From 55f9db2d204ab35719acf671c2578513f2bd419d Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 8 Oct 2016 00:39:26 +0200 Subject: [PATCH 01/92] Fixed #184 - Problem with endless scrolling and LOAD_MORE_RESET delay --- build.gradle | 2 +- .../fragments/FragmentEndlessScrolling.java | 7 +-- .../flexibleadapter/FlexibleAdapter.java | 59 +++++++++---------- 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/build.gradle b/build.gradle index 54337a48..ca82af68 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ ext { //Library libraryCode = 18 - libraryVersion = "5.0.0-b8" + libraryVersion = "5.0.0-SNAPSHOT" libraryDate = " built on " + getDate() libraryDescription = "1 Adapter for SelectionMode, Undo, ViewHolders, Filter, FastScroller, Animations, Sticky Headers, Expandable, Draggable, Swipeable, EndlessScroll :-)" libraryName = "FlexibleAdapter" 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..441e8e10 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 @@ -65,7 +65,7 @@ public void onActivityCreated(Bundle savedInstanceState) { FlipView.resetLayoutAnimationDelay(true, 1000L); //Create New Database and Initialize RecyclerView - DatabaseService.getInstance().createEndlessDatabase(100);//N. of items + DatabaseService.getInstance().createEndlessDatabase(1);//N. of items initializeRecyclerView(savedInstanceState); //Settings for FlipView @@ -132,7 +132,6 @@ public void onLoadMore() { mAdapter.onLoadMoreComplete(null); return; } - Log.i(TAG, "onLoadMore invoked!"); //Simulating asynchronous call new Handler().postDelayed(new Runnable() { @Override @@ -140,7 +139,7 @@ public void run() { final List newItems = new ArrayList<>(); //Simulating success/failure - int count = new Random().nextInt(5); + int count = new Random().nextInt(3); int totalItemsOfType = mAdapter.getItemCountOfTypes(R.layout.recycler_expandable_item); for (int i = 1; i <= count; i++) { if (i % 2 != 0) { @@ -171,7 +170,7 @@ public void run() { //Notify user String message = (newItems.size() > 0 ? "Simulated: " + newItems.size() + " new items arrived :-)" : - "Simulated: No more items to load :-("); + "Simulated: No more items to load :-(\nRefresh to retry."); Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); } }, 2500); 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..77ac9063 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -106,8 +106,7 @@ public class FlexibleAdapter */ private static final int UPDATE = 0, FILTER = 1, CONFIRM_DELETE = 2, - LOAD_MORE_COMPLETE = 8, LOAD_MORE_RESET = 9; - + LOAD_MORE_COMPLETE = 8; /** * The main container for ALL items. @@ -127,8 +126,7 @@ public class FlexibleAdapter *
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.

+ *
8 = remove the progress item from the list, optionally delayed.

* Note: numbers 0-9 are reserved for the Adapter, use others. */ protected Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { @@ -148,9 +146,6 @@ public boolean handleMessage(Message message) { case LOAD_MORE_COMPLETE: //onLoadMore remove progress item deleteProgressItem(); return true; - case LOAD_MORE_RESET: //onLoadMore reset - resetOnLoadMore(); - return true; } return false; } @@ -1467,17 +1462,28 @@ public FlexibleAdapter setEndlessScrollThreshold(@IntRange(from = 1) int thresho * @param position the current binding position */ protected void onLoadMore(int position) { - if (mProgressItem != null && !mLoading - && position >= getItemCount() - mEndlessScrollThreshold - && getGlobalPositionOf(mProgressItem) < 0) { + //Skip everything when no loading more + if (mProgressItem == null || position == getGlobalPositionOf(mProgressItem)) { + return; + } else if (DEBUG) { + Log.v(TAG, "onLoadMore loading=" + mLoading + ", position=" + position + + ", itemCount=" + getItemCount() + ", threshold=" + mEndlessScrollThreshold + + ", inside the threshold? " + (position >= getItemCount() - mEndlessScrollThreshold)); + } + //Load more if not loading and inside the threshold + if (!mLoading && position >= getItemCount() - mEndlessScrollThreshold) { mLoading = true; + if (DEBUG) Log.d(TAG, "onLoadMore invoked!"); mRecyclerView.post(new Runnable() { @Override public void run() { - mItems.add(mProgressItem); - notifyItemInserted(getItemCount()); - if (mEndlessScrollListener != null) + if (getGlobalPositionOf(mProgressItem) < 0) { + mItems.add(mProgressItem); + notifyItemInserted(getItemCount()); + } + if (mEndlessScrollListener != null) { mEndlessScrollListener.onLoadMore(); + } } }); } @@ -1505,25 +1511,24 @@ public void onLoadMoreComplete(@Nullable List newItems) { * * @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 */ 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 + //Disable the Automatic Endless functionality and keep the item mProgressItem = null; } else { //Delete the progress item with delay mHandler.sendEmptyMessageDelayed(LOAD_MORE_COMPLETE, delay); } //Add the new items or reset the loading status + mLoading = false; if (newItems != null && newItems.size() > 0) { if (DEBUG) - Log.i(TAG, "onLoadMore performing adding " + newItems.size() + " new Items!"); + Log.i(TAG, "onLoadMore performing adding " + newItems.size() + " new Items!"); addItems(getItemCount(), newItems); - //Reset OnLoadMore delayed - mHandler.sendEmptyMessageDelayed(LOAD_MORE_RESET, 200L); } else { noMoreLoad(); } @@ -1544,14 +1549,8 @@ private void deleteProgressItem() { * Called when no more items are loaded. */ private void noMoreLoad() { - if (DEBUG) Log.v(TAG, "onLoadMore noMoreLoad!"); + if (DEBUG) Log.i(TAG, "onLoadMore noMoreLoad!"); notifyItemChanged(getItemCount() - 1, Payload.NO_MORE_LOAD); - //Reset OnLoadMore delayed - mHandler.sendEmptyMessageDelayed(LOAD_MORE_RESET, 200L); - } - - private void resetOnLoadMore() { - mLoading = false; } /*--------------------*/ @@ -1853,11 +1852,11 @@ 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) { + 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)) { From 8a1f63eccb728f825b65c8da39263305731bd019 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 8 Oct 2016 00:51:08 +0200 Subject: [PATCH 02/92] Fixed #191 - AsyncFilter fatal error (Empty string will skip the search) --- .../flexibleadapter/FlexibleAdapter.java | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) 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 77ac9063..a3c0fba9 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -3066,6 +3066,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 @@ -3082,7 +3083,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, @@ -3145,8 +3146,8 @@ public void filterItems(@NonNull List unfilteredItems, @IntRange(from = 0) lo * 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 + *
  • NEW! Items are animated thanks to {@link #animateTo(List, Payload)} 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.
  • * @@ -3168,11 +3169,11 @@ private synchronized void filterItemsAsync(@NonNull List unfilteredItems) { // 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! - if (hasSearchText()) { + if (hasSearchText() && hasNewSearchText(mSearchText)) { //skip when text is unchanged int newOriginalPosition = -1; for (T item : unfilteredItems) { if (mFilterAsyncTask != null && mFilterAsyncTask.isCancelled()) return; @@ -3223,7 +3224,7 @@ private synchronized void filterItemsAsync(@NonNull List unfilteredItems) { //Animate search results only in case of new SearchText if (hasNewSearchText(mSearchText)) { mOldSearchText = mSearchText; - animateTo(filteredItems); + animateTo(filteredItems, Payload.FILTER); } } @@ -3372,7 +3373,7 @@ 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) { + private synchronized void animateTo(@Nullable List newItems, Payload payloadChange) { if (newItems == null) newItems = new ArrayList<>(); notifications = new ArrayList<>(); if (newItems.size() <= mAnimateToLimit) { @@ -3390,7 +3391,7 @@ private synchronized void animateTo(@Nullable List newItems) { notifications.add(new Notification(-1, 0)); } //Execute All notifications if filter was Synchronous! - if (mFilterAsyncTask == null) executeNotifications(); + if (mFilterAsyncTask == null) executeNotifications(payloadChange); } /** @@ -3473,7 +3474,7 @@ private void applyAndAnimateMovedItems(List from, List newItems) { if (DEBUG) Log.v(TAG, "calculateMovedItems total move=" + move); } - private synchronized void executeNotifications() { + private synchronized void executeNotifications(Payload payloadChange) { if (DEBUG) Log.i(TAG, "Performing " + notifications.size() + " notifications"); mItems = mTempItems;// Update mItems in the UI Thread setAnimate(false);//Disable scroll animation @@ -3483,7 +3484,7 @@ private synchronized void executeNotifications() { notifyItemInserted(notification.position); break; case Notification.CHANGE: - notifyItemChanged(notification.position, Payload.FILTER); + notifyItemChanged(notification.position, payloadChange); break; case Notification.REMOVE: notifyItemRemoved(notification.position); @@ -4346,7 +4347,7 @@ protected Void doInBackground(Void... params) { switch (what) { case UPDATE: if (DEBUG) Log.d(TAG, "doInBackground - started UPDATE"); - animateTo(newItems); + animateTo(newItems, Payload.CHANGE); if (DEBUG) Log.d(TAG, "doInBackground - ended UPDATE"); break; case FILTER: @@ -4360,16 +4361,20 @@ 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 (notifications != 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; } From aad4258b60a37188691ed9869deaa985b548306c Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 8 Oct 2016 01:43:19 +0200 Subject: [PATCH 03/92] Fixed #176 - Drop event, without swapItems on move (ItemTouchHelperCallback can now be set) --- .../flexibleadapter/FlexibleAdapter.java | 25 ++++++++++++++++--- .../helpers/ItemTouchHelperCallback.java | 19 +++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) 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 a3c0fba9..fbade461 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -3529,6 +3529,21 @@ public final ItemTouchHelperCallback getItemTouchHelperCallback() { return mItemTouchHelperCallback; } + /** + * Sets a custom callback implementation for Item touch. + *

    Helper will be reinitialized.

    + * + * @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(); + return this; + } + /** * Returns whether ItemTouchHelper should start a drag and drop operation if an item is * long pressed.

    @@ -3683,9 +3698,9 @@ public void swapItems(List list, int fromPosition, int toPosition) { } //Collapse expandable before swapping (otherwise items are mixed badly) -// if (fromPosition < toPosition && isExpandable(getItem(fromPosition)) && isExpanded(toPosition)) { -// collapse(toPosition); -// } + if (fromPosition < toPosition && isExpandable(getItem(fromPosition)) && isExpanded(toPosition)) { + collapse(toPosition); + } //Perform item swap (for all LayoutManagers) if (fromPosition < toPosition) { @@ -3822,7 +3837,9 @@ private void initializeItemTouchHelper() { 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); + if (mItemTouchHelperCallback == null) { + mItemTouchHelperCallback = new ItemTouchHelperCallback(this); + } mItemTouchHelper = new ItemTouchHelper(mItemTouchHelperCallback); mItemTouchHelper.attachToRecyclerView(mRecyclerView); } diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ItemTouchHelperCallback.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ItemTouchHelperCallback.java index 97225023..d0593b57 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ItemTouchHelperCallback.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ItemTouchHelperCallback.java @@ -32,21 +32,22 @@ import eu.davidea.viewholders.FlexibleViewHolder; /** - * This class is an implementation of {@link Callback} that enables drag & drop - * and swipe actions. Drag and Swipe events are started depending by its configuration. + * This class is an implementation of {@link Callback} that enables Drag & Drop + * and Swipe actions. Drag and Swipe events are started depending by the configuration. * * @author Davide Steduto * @since 23/01/2016 Created */ +@SuppressWarnings({"WeakerAccess", "unused"}) public class ItemTouchHelperCallback extends Callback { - private static final float ALPHA_FULL = 1.0f; + protected static final float ALPHA_FULL = 1.0f; - private AdapterCallback mItemTouchCallback; - private boolean mIsLongPressDragEnabled = false, mIsSwipeEnabled = false; - private long mSwipeAnimationDuration = 300L, mDragAnimationDuration = 400L; - private float mSwipeThreshold = 0.5f, mMoveThreshold = 0.5f; - private int mSwipeFlags = -1; + protected AdapterCallback mItemTouchCallback; + protected boolean mIsLongPressDragEnabled = false, mIsSwipeEnabled = false; + protected long mSwipeAnimationDuration = 300L, mDragAnimationDuration = 400L; + protected float mSwipeThreshold = 0.5f, mMoveThreshold = 0.5f; + protected int mSwipeFlags = -1; /*-------------*/ /* CONSTRUCTOR */ @@ -84,7 +85,7 @@ public boolean isLongPressDragEnabled() { */ @Override public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) { - return current.getItemViewType() == target.getItemViewType(); + return true; } /** From 55136c1a5867cac4faceb9d35138677e49118714 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 9 Oct 2016 13:37:54 +0200 Subject: [PATCH 04/92] Update ISSUE_TEMPLATE --- ISSUE_TEMPLATE.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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; From 761bc840192b199960bbd52fa769396e9dba289d Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 9 Oct 2016 14:32:34 +0200 Subject: [PATCH 05/92] Upgrade buildTools to 24.0.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ca82af68..4a6682df 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ ext { //Support and Build tools version minSdk = 14 targetSdk = 24 - buildTools = "23.0.3" + buildTools = "24.0.2" supportLib = "24.2.1" //Support Libraries dependencies From f92eecc8559732127b55a5ff8e26da68a608ceee Mon Sep 17 00:00:00 2001 From: Jean Pimentel Date: Mon, 10 Oct 2016 14:11:09 -0300 Subject: [PATCH 06/92] Implements method to set snackbar action text color (#193) --- .../samples/flexibleadapter/MainActivity.java | 11 ++++++++ .../flexibleadapter/helpers/UndoHelper.java | 28 ++++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) 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..82b25c84 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,7 @@ import android.app.SearchManager; import android.content.Context; +import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -711,6 +712,15 @@ public void onItemSwipe(final int position, int direction) { //Here, option 2A) is implemented if (direction == ItemTouchHelper.LEFT) { message.append(getString(R.string.action_archived)); + + //Example of UNDO color + int actionTextColor = Color.TRANSPARENT; + if (Utils.hasMarshmallow()) { + actionTextColor = getResources().getColor(R.color.material_color_orange_500, this.getTheme()); + } else if (Utils.hasLollipop()) { + 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) .withAction(UndoHelper.ACTION_UPDATE, new UndoHelper.SimpleActionListener() { @@ -722,6 +732,7 @@ public boolean onPreAction() { return true; } }) + .withActionTextColor(actionTextColor) .remove(positions, findViewById(R.id.main_view), message, getString(R.string.undo), UndoHelper.UNDO_TIMEOUT); diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java index 42439469..e624f991 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java @@ -16,6 +16,8 @@ package eu.davidea.flexibleadapter.helpers; import android.content.Context; +import android.graphics.Color; +import android.support.annotation.ColorInt; import android.support.annotation.IntDef; import android.support.annotation.IntRange; import android.support.annotation.NonNull; @@ -65,6 +67,7 @@ public class UndoHelper extends Snackbar.Callback { private FlexibleAdapter mAdapter; private OnActionListener mActionListener; private OnUndoListener mUndoListener; + private @ColorInt int mActionTextColor = Color.TRANSPARENT; /** @@ -107,6 +110,17 @@ public UndoHelper withAction(@Action int action, @NonNull OnActionListener actio return this; } + /** + * Sets the text color of the action. + * + * @param color the color for the action button + * @return this object, so it can be chained + */ + public UndoHelper withActionTextColor(@ColorInt int color) { + this.mActionTextColor = color; + return this; + } + /** * As {@link #remove(List, View, CharSequence, CharSequence, int)} but with String * resources instead of CharSequence. @@ -142,7 +156,6 @@ public Snackbar remove(List positions, @NonNull View mainView, Snackbar snackbar; if (!mAdapter.isPermanentDelete()) { snackbar = Snackbar.make(mainView, message, undoTime + 400)//More time due to the animation - .setCallback(this) .setAction(actionText, new View.OnClickListener() { @Override public void onClick(View v) { @@ -150,14 +163,15 @@ public void onClick(View v) { mUndoListener.onUndoConfirmed(mAction); } }); - snackbar.show(); - return snackbar; } else { - snackbar = Snackbar.make(mainView, message, undoTime) - .setCallback(this); - snackbar.show(); - return snackbar; + snackbar = Snackbar.make(mainView, message, undoTime); + } + if (mActionTextColor != Color.TRANSPARENT) { + snackbar.setActionTextColor(mActionTextColor); } + snackbar.setCallback(this); + snackbar.show(); + return snackbar; } /** From 4f6491e787032f10dacd0ec3903459615c1e0957 Mon Sep 17 00:00:00 2001 From: Boris D'Amato Date: Mon, 10 Oct 2016 19:12:25 +0200 Subject: [PATCH 07/92] Added the ability to manually set the stickyContainer ViewGroup (#195) --- .../eu/davidea/flexibleadapter/FlexibleAdapter.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 fbade461..f3a8fee3 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -161,6 +161,7 @@ public boolean handleMessage(Message message) { private List mOrphanHeaders; private boolean headersShown = false, headersSticky = false, recursive = false; private StickyHeaderHelper mStickyHeaderHelper; + private ViewGroup mStickyContainer; /* ViewTypes */ protected LayoutInflater mInflater; @@ -984,7 +985,11 @@ public void run() { * @since 5.0.0-b6 */ public ViewGroup getStickySectionHeadersHolder() { - return (ViewGroup) Utils.scanForActivity(mRecyclerView.getContext()).findViewById(R.id.sticky_header_container); + if (mStickyContainer != null) { + return mStickyContainer; + } else { + return (ViewGroup) Utils.scanForActivity(mRecyclerView.getContext()).findViewById(R.id.sticky_header_container); + } } /** @@ -1277,6 +1282,11 @@ private boolean isHeaderShared(IHeader header, int positionStart, int itemCount) return false; } + public FlexibleAdapter setStickyHeaderContainer(@Nullable ViewGroup stickyContainer) { + this.mStickyContainer = stickyContainer; + return this; + } + /*---------------------*/ /* VIEW HOLDER METHODS */ /*---------------------*/ From 7ecdd32c33a3c1d59dd6fc66e67244de59e88b44 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 11 Oct 2016 01:22:58 +0200 Subject: [PATCH 08/92] Small optimizations after the Pull Requests. Resolves #179 and resolves #190. --- .../flexibleadapter/FlexibleAdapter.java | 116 ++++++++++-------- .../helpers/StickyHeaderHelper.java | 2 +- .../flexibleadapter/helpers/UndoHelper.java | 7 +- .../davidea/flexibleadapter/utils/Utils.java | 17 ++- 4 files changed, 75 insertions(+), 67 deletions(-) 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 f3a8fee3..17a7fe3f 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -116,8 +116,8 @@ public class FlexibleAdapter /** * HashSet and AsyncTask objects, will increase performance in big list */ - private Set hashItems; - private List notifications; + private Set mHashItems; + private List mNotifications; private FilterAsyncTask mFilterAsyncTask; /** @@ -177,7 +177,7 @@ public boolean handleMessage(Message message) { private static int mAnimateToLimit = 600; /* Expandable flags */ - private int minCollapsibleLevel = 0, selectedLevel = -1; + private int mMinCollapsibleLevel = 0, mSelectedLevel = -1; private boolean scrollOnExpand = false, collapseOnExpand = false, childSelected = false, parentSelected = false; @@ -388,20 +388,20 @@ public void toggleSelection(@IntRange(from = 0) int position) { if ((isExpandable(item) || !hasParent) && !childSelected) { //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) { + } 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 if (getSelectedItemCount() == 0) { - selectedLevel = -1; + mSelectedLevel = -1; parentSelected = childSelected = false; } } @@ -821,7 +821,7 @@ 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? + //TODO: rename to getSectionHeaderByPosition? public IHeader getSectionHeader(@IntRange(from = 0) int position) { //Headers are not visible nor sticky if (!headersShown) return null; @@ -984,12 +984,26 @@ public void run() { * @return ViewGroup layout that will hold the sticky header ItemViews * @since 5.0.0-b6 */ + //TODO: Rename the method to getStickyHeaderContainer public ViewGroup getStickySectionHeadersHolder() { - if (mStickyContainer != null) { - return mStickyContainer; - } else { - 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 a custom {@link ViewGroup} for Sticky Headers when the default can't be used. + *

    Useful in conjunction with ViewPager.

    + * + * @param stickyContainer custom container for Sticky Headers + * @since 5.0.0-rc1 + */ + public FlexibleAdapter setStickyHeaderContainer(@Nullable ViewGroup stickyContainer) { + this.mStickyContainer = stickyContainer; + return this; } /** @@ -1282,11 +1296,6 @@ private boolean isHeaderShared(IHeader header, int positionStart, int itemCount) return false; } - public FlexibleAdapter setStickyHeaderContainer(@Nullable ViewGroup stickyContainer) { - this.mStickyContainer = stickyContainer; - return this; - } - /*---------------------*/ /* VIEW HOLDER METHODS */ /*---------------------*/ @@ -1649,19 +1658,19 @@ public boolean isExpandable(@NonNull T item) { * @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; + this.mMinCollapsibleLevel = minCollapsibleLevel; return this; } @@ -1869,11 +1878,11 @@ private int expand(int position, boolean expandAll, boolean init) { } 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) { + if (collapseOnExpand && !expandAll && collapseAll(mMinCollapsibleLevel) > 0) { position = getGlobalPositionOf(item); } @@ -1909,7 +1918,7 @@ private int expand(int position, boolean expandAll, boolean init) { } /** - * 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) @@ -1917,7 +1926,7 @@ private int expand(int position, boolean expandAll, boolean init) { * @since 5.0.0-b1 */ public int expandAll() { - return expandAll(minCollapsibleLevel); + return expandAll(mMinCollapsibleLevel); } /** @@ -1963,7 +1972,7 @@ public int collapse(@IntRange(from = 0) int position) { 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)); @@ -1975,7 +1984,7 @@ public int collapse(@IntRange(from = 0) int position) { //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); + if (mHashItems != null) mHashItems.removeAll(subItems); else mItems.removeAll(subItems); subItemsCount = subItems.size(); //Save expanded state @@ -2011,7 +2020,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) @@ -2020,7 +2029,7 @@ private int recursiveCollapse(int startPosition, List subItems, int level) { * @since 5.0.0-b1 */ public int collapseAll() { - return collapseAll(minCollapsibleLevel); + return collapseAll(mMinCollapsibleLevel); } /** @@ -2032,10 +2041,10 @@ public int collapseAll() { * @since 5.0.0-b6 */ public int collapseAll(int level) { - hashItems = new LinkedHashSet<>(mItems); + mHashItems = new LinkedHashSet<>(mItems); int collapsed = recursiveCollapse(0, mItems, level); - mItems = new ArrayList<>(hashItems); - hashItems = null; + mItems = new ArrayList<>(mHashItems); + mHashItems = null; return collapsed; } @@ -3371,7 +3380,7 @@ public FlexibleAdapter setAnimateToLimit(int limit) { /** * 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.

    @@ -3385,7 +3394,7 @@ public FlexibleAdapter setAnimateToLimit(int limit) { */ private synchronized void animateTo(@Nullable List newItems, Payload payloadChange) { if (newItems == null) newItems = new ArrayList<>(); - notifications = new ArrayList<>(); + mNotifications = new ArrayList<>(); if (newItems.size() <= mAnimateToLimit) { if (DEBUG) Log.v(TAG, "Animate changes! oldSize=" + getItemCount() + " newSize=" + newItems.size() + " limit=" + mAnimateToLimit); @@ -3398,7 +3407,7 @@ private synchronized void animateTo(@Nullable List newItems, Payload payloadC 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(payloadChange); @@ -3411,23 +3420,23 @@ private synchronized void animateTo(@Nullable List newItems, Payload payloadC */ private void applyAndAnimateRemovals(List from, List newItems) { //Using Hash for performance - hashItems = new HashSet<>(newItems); + mHashItems = new HashSet<>(newItems); int out = 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) && (!isHeader(item) || (isHeader(item) && headersShown))) { //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)); + mNotifications.add(new Notification(i, Notification.CHANGE)); //if (DEBUG) Log.v(TAG, "calculateRemovals keep position=" + i + " item=" + item + " searchText=" + mSearchText); } } - hashItems = null; + mHashItems = null; if (DEBUG) Log.v(TAG, "calculateRemovals total out=" + out); } @@ -3438,25 +3447,25 @@ private void applyAndAnimateRemovals(List from, List newItems) { */ private void applyAndAnimateAdditions(List from, List newItems) { //Using Hash for performance - hashItems = new HashSet<>(from); + 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 (!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 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); } @@ -3477,7 +3486,7 @@ private void applyAndAnimateMovedItems(List from, List newItems) { 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++; } } @@ -3485,10 +3494,10 @@ private void applyAndAnimateMovedItems(List from, List newItems) { } private synchronized void executeNotifications(Payload payloadChange) { - if (DEBUG) Log.i(TAG, "Performing " + notifications.size() + " notifications"); + if (DEBUG) Log.i(TAG, "Performing " + mNotifications.size() + " notifications"); mItems = mTempItems;// Update mItems in the UI Thread setAnimate(false);//Disable scroll animation - for (Notification notification : notifications) { + for (Notification notification : mNotifications) { switch (notification.operation) { case Notification.ADD: notifyItemInserted(notification.position); @@ -3509,7 +3518,7 @@ private synchronized void executeNotifications(Payload payloadChange) { } } mTempItems = null; - notifications = null; + mNotifications = null; } /*---------------*/ @@ -4033,7 +4042,7 @@ public void onSaveInstanceState(Bundle outState) { //Save selection coherence outState.putBoolean(EXTRA_CHILD, this.childSelected); outState.putBoolean(EXTRA_PARENT, this.parentSelected); - outState.putInt(EXTRA_LEVEL, this.selectedLevel); + 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 @@ -4062,7 +4071,7 @@ public void onRestoreInstanceState(Bundle savedInstanceState) { //Restore selection coherence this.parentSelected = savedInstanceState.getBoolean(EXTRA_PARENT); this.childSelected = savedInstanceState.getBoolean(EXTRA_CHILD); - this.selectedLevel = savedInstanceState.getInt(EXTRA_LEVEL); + this.mSelectedLevel = savedInstanceState.getInt(EXTRA_LEVEL); //Current filter (old text must not be saved) this.mSearchText = savedInstanceState.getString(EXTRA_SEARCH); } @@ -4388,7 +4397,7 @@ protected Void doInBackground(Void... params) { @Override protected void onPostExecute(Void result) { - if (notifications != null) { + if (mNotifications != null) { //Execute post data switch (what) { case UPDATE: @@ -4408,7 +4417,8 @@ protected void onPostExecute(Void result) { } /** - * @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 diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index 46403f05..7e08a7e7 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -229,7 +229,7 @@ private void ensureHeaderParent() { mStickyHolderLayout.addView(view); } - public void clearHeader() { + private void clearHeader() { if (mStickyHeaderViewHolder != null) { if (FlexibleAdapter.DEBUG) Log.d(TAG, "clearHeader"); resetHeader(mStickyHeaderViewHolder); diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java index e624f991..566fc4d7 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java @@ -37,6 +37,7 @@ * @author Davide Steduto * @since 30/04/2016 */ +@SuppressWarnings("WeakerAccess") public class UndoHelper extends Snackbar.Callback { /** @@ -181,15 +182,15 @@ public void onClick(View v) { public void onDismissed(Snackbar snackbar, int event) { if (mAdapter.isPermanentDelete()) return; switch (event) { - case DISMISS_EVENT_ACTION: - //We ignore it, action is performed already - break; case DISMISS_EVENT_SWIPE: case DISMISS_EVENT_MANUAL: case DISMISS_EVENT_TIMEOUT: if (mUndoListener != null) mUndoListener.onDeleteConfirmed(mAction); mAdapter.emptyBin(); + case DISMISS_EVENT_CONSECUTIVE: + case DISMISS_EVENT_ACTION: + default: break; } } diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java index 5af4c249..433efa4b 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java @@ -15,7 +15,6 @@ */ package eu.davidea.flexibleadapter.utils; -import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; @@ -36,6 +35,8 @@ import java.util.Locale; +import eu.davidea.flexibleadapter.R; + /** * @author Davide Steduto * @since 27/01/2016 Created @@ -107,17 +108,13 @@ public static void resetAccentColor() { * @param context context * @param defColor value to return if the accentColor cannot be found */ - //TODO: Deprecate defColor and use R.attr.colorAccent? - @TargetApi(VERSION_CODES.LOLLIPOP) public static int fetchAccentColor(Context context, @ColorInt int defColor) { if (colorAccent == INVALID_COLOR) { - if (hasLollipop()) { - TypedArray androidAttr = context.getTheme().obtainStyledAttributes(new int[]{android.R.attr.colorAccent}); - colorAccent = androidAttr.getColor(0, defColor); - androidAttr.recycle(); - } else { - colorAccent = defColor; - } + int attr = R.attr.colorAccent; + if (hasLollipop()) attr = android.R.attr.colorAccent; + TypedArray androidAttr = context.getTheme().obtainStyledAttributes(new int[]{attr}); + colorAccent = androidAttr.getColor(0, defColor); + androidAttr.recycle(); } return colorAccent; } From 8e340152d117d3789e70b3acef6aa7873c339ec7 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 11 Oct 2016 01:23:50 +0200 Subject: [PATCH 09/92] Added ViewPager example usage with StickyHeaders --- .../src/main/AndroidManifest.xml | 16 +- .../samples/flexibleadapter/MainActivity.java | 15 ++ .../flexibleadapter/ViewPagerActivity.java | 142 +++++++++++++++ .../fragments/FragmentAsyncFilter.java | 2 +- .../fragments/FragmentEndlessScrolling.java | 2 +- .../FragmentExpandableMultiLevel.java | 2 +- .../fragments/FragmentExpandableSections.java | 2 +- .../fragments/FragmentHeadersSections.java | 2 +- .../fragments/FragmentSelectionModes.java | 2 +- .../fragments/FragmentViewPager.java | 167 ++++++++++++++++++ .../services/DatabaseService.java | 4 + .../main/res/layout/activity_view_pager.xml | 47 +++++ .../main/res/layout/fragment_view_pager.xml | 53 ++++++ .../main/res/menu/activity_entry_drawer.xml | 28 +-- .../src/main/res/menu/menu_view_pager.xml | 16 ++ .../src/main/res/values/dimens.xml | 1 + .../src/main/res/values/strings.xml | 6 +- 17 files changed, 481 insertions(+), 26 deletions(-) create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ViewPagerActivity.java create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentViewPager.java create mode 100644 flexible-adapter-app/src/main/res/layout/activity_view_pager.xml create mode 100644 flexible-adapter-app/src/main/res/layout/fragment_view_pager.xml create mode 100644 flexible-adapter-app/src/main/res/menu/menu_view_pager.xml 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/MainActivity.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/MainActivity.java index 82b25c84..9634a72e 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 @@ -3,6 +3,7 @@ import android.app.SearchManager; import android.content.Context; import android.graphics.Color; +import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -12,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; @@ -344,6 +347,18 @@ 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_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, 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..d8c8a523 --- /dev/null +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ViewPagerActivity.java @@ -0,0 +1,142 @@ +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; + +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/FragmentAsyncFilter.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentAsyncFilter.java index 8638c8a0..0a4bdba2 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 @@ -128,7 +128,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 441e8e10..9b643ea6 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 @@ -94,7 +94,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { 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), + 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 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..5adb483d 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 @@ -80,7 +80,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { 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), + 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 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..0bbb6a30 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 @@ -89,7 +89,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { 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), + 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 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..939249ec 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 @@ -94,7 +94,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { 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), + mAdapter.setFastScroller((FastScroller) getView().findViewById(R.id.fast_scroller), Utils.getColorAccent(getActivity()), (MainActivity) getActivity()); mAdapter.setLongPressDragEnabled(true) .setHandleDragEnabled(true) 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..7ed88117 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 @@ -116,7 +116,7 @@ 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), + mAdapter.setFastScroller((FastScroller) getView().findViewById(R.id.fast_scroller), Utils.getColorAccent(getActivity()), (MainActivity) getActivity()); SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout); 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..152a743c --- /dev/null +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentViewPager.java @@ -0,0 +1,167 @@ +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.models.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(savedInstanceState); + + //Settings for FlipView + FlipView.stopLayoutAnimation(); + } + + private void initializeRecyclerView(Bundle savedInstanceState) { + //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.setStickyHeaderContainer((ViewGroup) getView().findViewById(R.id.sticky_header_container)) + .setDisplayHeadersAtStartUp(true) + .enableStickyHeaders(); + } + + 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) { + if (mAdapter.areHeadersSticky()) { + item.setChecked(false); + mAdapter.disableStickyHeaders(); + } else { + item.setChecked(true); + mAdapter.enableStickyHeaders(); + } + } 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/services/DatabaseService.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/services/DatabaseService.java index c3238f35..26f3e489 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 @@ -122,6 +122,10 @@ public void createOverallDatabase(Resources resources) { 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))); } /* 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_view_pager.xml b/flexible-adapter-app/src/main/res/layout/fragment_view_pager.xml new file mode 100644 index 00000000..fb93dc06 --- /dev/null +++ b/flexible-adapter-app/src/main/res/layout/fragment_view_pager.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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..dc50d67c 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 @@ -54,27 +54,27 @@ android:enabled="false" android:icon="@drawable/ic_view_column_grey600_24dp" android:title="@string/horizontal"/> - - + - - + + + + - - + + 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/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..73ec6ce6 100644 --- a/flexible-adapter-app/src/main/res/values/strings.xml +++ b/flexible-adapter-app/src/main/res/values/strings.xml @@ -115,10 +115,10 @@ items and higher, due to calculate the correct position for each item to shift. 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 View Pager - Example usage in ViewPager layout + Example usage with Sticky Headers in ViewPager layout Dialogs Example usage in Dialogs @@ -152,5 +152,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 From c95b8751a08f64212f718fc5b03912ba69145b33 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Thu, 13 Oct 2016 01:19:50 +0200 Subject: [PATCH 10/92] Resolves #168 - Add Info log for parameters and listeners initialization --- .../flexibleadapter/ExampleAdapter.java | 2 +- .../fragments/FragmentEndlessScrolling.java | 10 +- .../models/AnimatorExpandableItem.java | 2 +- .../models/ExpandableHeaderItem.java | 2 +- .../models/ExpandableLevel0Item.java | 2 +- .../models/ExpandableLevel1Item.java | 2 +- .../flexibleadapter/models/HeaderItem.java | 2 +- .../models/InstagramHeaderItem.java | 2 +- .../flexibleadapter/models/SimpleItem.java | 2 +- .../models/StaggeredHeaderItem.java | 2 +- .../showcase/ExpandableHeaderItemExample.java | 2 +- .../flexibleadapter/AnimatorAdapter.java | 45 ++++---- .../flexibleadapter/FlexibleAdapter.java | 100 ++++++++++++------ .../flexibleadapter/SelectableAdapter.java | 10 ++ .../helpers/AnimatorHelper.java | 4 - .../helpers/ItemTouchHelperCallback.java | 31 ++++-- .../helpers/StickyHeaderHelper.java | 2 +- 17 files changed, 139 insertions(+), 83 deletions(-) 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..09cfc732 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 @@ -29,7 +29,7 @@ public class ExampleAdapter extends FlexibleAdapter { private AbstractFlexibleItem mUseCaseItem; 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); } 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 9b643ea6..f04e0582 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 @@ -79,7 +79,6 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity()); //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 @@ -97,9 +96,10 @@ private void initializeRecyclerView(Bundle savedInstanceState) { 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! + mAdapter.setLongPressDragEnabled(true)//Enable long press to drag items + .setHandleDragEnabled(true)//Enable drag using handle view + .setSwipeEnabled(true)//Enable swipe items + .setDisplayHeadersAtStartUp(true);//Show Headers at startUp! SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout); swipeRefreshLayout.setEnabled(true); @@ -107,7 +107,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { //EndlessScrollListener - OnLoadMore (v5.0.0) mAdapter.setEndlessScrollListener(this, new ProgressItem()); - mAdapter.setEndlessScrollThreshold(1);//Default=1 + //mAdapter.setEndlessScrollThreshold(1);//Default=1 //Add sample HeaderView items on the top (not belongs to the library) mAdapter.addUserLearnedSelection(savedInstanceState == null); 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/models/AnimatorExpandableItem.java index 7a33265e..fa2c95bb 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/models/AnimatorExpandableItem.java @@ -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/ExpandableHeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableHeaderItem.java index 434b424e..5b0a6bf3 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/models/ExpandableHeaderItem.java @@ -109,7 +109,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()); } 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/models/ExpandableLevel0Item.java index 712a28c0..d6200851 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/models/ExpandableLevel0Item.java @@ -109,7 +109,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/models/ExpandableLevel1Item.java index b17830a9..cd1fbaf8 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/models/ExpandableLevel1Item.java @@ -97,7 +97,7 @@ public SimpleItem.ParentViewHolder createViewHolder(FlexibleAdapter adapter, Lay @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); + 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/HeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderItem.java index 27ef51c6..73f0148b 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/models/HeaderItem.java @@ -88,7 +88,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()); } 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/models/InstagramHeaderItem.java index 9a0a86b6..26e5867d 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/models/InstagramHeaderItem.java @@ -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()); } 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/models/SimpleItem.java index 2a973054..b7d9dcdb 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/models/SimpleItem.java @@ -87,7 +87,7 @@ public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holde int defColorAccent = context.getResources().getColor(R.color.colorAccent_light); if (adapter.isExpandable(this) && payloads.size() > 0) { - Log.i(this.getClass().getSimpleName(), "ExpandableItem Payload " + payloads); + Log.d(this.getClass().getSimpleName(), "ExpandableItem Payload " + payloads); if (adapter.hasSearchText()) { Utils.highlightText(holder.itemView.getContext(), holder.mSubtitle, getSubtitle(), adapter.getSearchText(), defColorAccent); 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/models/StaggeredHeaderItem.java index 3df16546..e645d16f 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/models/StaggeredHeaderItem.java @@ -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); 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 index ae62c3c5..77340056 100644 --- 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 @@ -97,7 +97,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()); } 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..55ea71c2 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -21,7 +21,6 @@ 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; @@ -58,7 +57,7 @@ @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; @@ -104,9 +103,9 @@ private enum AnimatorEnum { * * @since 5.0.0-b1 */ - public AnimatorAdapter(boolean stableIds) { + AnimatorAdapter(boolean stableIds) { super(); - if (stableIds && DEBUG) Log.i(TAG, "Setting StableIds"); + if (stableIds && DEBUG) Log.i("FlexibleAdapter", "initialize with StableIds"); setHasStableIds(stableIds); //Get notified when an item is changed (should skip animation) @@ -136,6 +135,7 @@ 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; } @@ -150,6 +150,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,6 +166,7 @@ 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; } @@ -178,6 +180,7 @@ 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; } @@ -190,6 +193,7 @@ public AnimatorAdapter setAnimationDuration(@IntRange(from = 1) long duration) { * @return this AnimatorAdapter, so the call can be chained */ public AnimatorAdapter setAnimationInterpolator(@NonNull Interpolator interpolator) { + if (DEBUG) Log.i(TAG, "Set animationInterpolator=" + interpolator.getClass().getSimpleName()); mInterpolator = interpolator; return this; } @@ -202,6 +206,7 @@ public AnimatorAdapter setAnimationInterpolator(@NonNull Interpolator interpolat * @since 5.0.0-b1 */ public AnimatorAdapter setAnimationStartPosition(@IntRange(from = 0) int start) { + if (DEBUG) Log.i(TAG, "Set animationStartPosition=" + start); mLastAnimatedPosition = start; return this; } @@ -221,6 +226,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; @@ -241,6 +247,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; } @@ -266,6 +273,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; @@ -296,18 +304,6 @@ public void onFastScrollerStateChange(boolean 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,9 +335,14 @@ private void cancelExistingAnimation(final int hashCode) { if (animator != null) animator.end(); } + /** + * Performs checks to scroll animate the itemView and in case, it animates the view. + * + * @param holder the ViewHolder just bound + * @param position the current item position + */ + //FIXME: first completed visible item on rotation gets high delay protected void animateView(final RecyclerView.ViewHolder holder, final int position) { - //FIXME: first completed visible item on rotation gets high delay - // if (DEBUG) // Log.v(TAG, "shouldAnimate=" + shouldAnimate // + " isFastScroll=" + isFastScroll @@ -376,7 +377,7 @@ protected void animateView(final RecyclerView.ViewHolder holder, final int posit } set.start(); mAnimators.put(hashCode, set); - if (DEBUG) Log.d(TAG, "Started Animation on position " + position); + if (DEBUG) Log.v(TAG, "animateView Scroll animation on position " + position); //Animate only during initial loading? if (onlyEntryAnimation && position >= mMaxChildViews) { @@ -418,7 +419,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(); @@ -671,13 +672,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; } 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 17a7fe3f..4f43ada6 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -61,7 +61,7 @@ /** * 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 + * interface. Read 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.

    @@ -182,7 +182,6 @@ public boolean handleMessage(Message message) { childSelected = false, parentSelected = false; /* Drag&Drop and Swipe helpers */ - private boolean handleDragEnabled = false; private ItemTouchHelperCallback mItemTouchHelperCallback; private ItemTouchHelper mItemTouchHelper; @@ -266,25 +265,37 @@ 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 * @since 5.0.0-b6 */ - public FlexibleAdapter initializeListeners(@Nullable Object listeners) { - if (listeners instanceof OnUpdateListener) { - mUpdateListener = (OnUpdateListener) listeners; + public FlexibleAdapter initializeListeners(@Nullable Object listener) { + if (DEBUG) Log.i(TAG, "Initialize Class " + listener.getClass().getSimpleName() + " 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(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; return this; } @@ -684,6 +695,7 @@ public boolean isRemoveOrphanHeaders() { */ //TODO: deprecation? public FlexibleAdapter setRemoveOrphanHeaders(boolean removeOrphanHeaders) { + if (DEBUG) Log.i(TAG, "Set removeOrphanHeaders=" + removeOrphanHeaders); this.removeOrphanHeaders = removeOrphanHeaders; return this; } @@ -698,6 +710,7 @@ public FlexibleAdapter setRemoveOrphanHeaders(boolean removeOrphanHeaders) { * @since 5.0.0-b6 */ public FlexibleAdapter setUnlinkAllItemsOnRemoveHeaders(boolean unlinkOnRemoveHeader) { + if (DEBUG) Log.i(TAG, "Set unlinkOnRemoveHeader=" + unlinkOnRemoveHeader); this.unlinkOnRemoveHeader = unlinkOnRemoveHeader; return this; } @@ -947,6 +960,7 @@ public void disableStickyHeaders() { } private FlexibleAdapter setStickyHeaders(final boolean sticky) { + if (DEBUG) Log.i(TAG, "Set stickyHeaders=" + sticky + " (in Post!)"); mHandler.post(new Runnable() { @Override public void run() { @@ -1002,6 +1016,7 @@ public ViewGroup getStickySectionHeadersHolder() { * @since 5.0.0-rc1 */ public FlexibleAdapter setStickyHeaderContainer(@Nullable ViewGroup stickyContainer) { + if (DEBUG) Log.i(TAG, "Set stickyHeaderContainer=" + stickyContainer.getClass().getSimpleName()); this.mStickyContainer = stickyContainer; return this; } @@ -1051,9 +1066,12 @@ public void showAllHeaders() { */ private void showAllHeaders(boolean init) { if (init) { + + if (DEBUG) Log.i(TAG, "showAllHeaders at startup"); //No notifyItemInserted! showAllHeadersWithReset(true); } else { + if (DEBUG) Log.i(TAG, "showAllHeaders with insert notification (in Post!)"); //In post, let's notifyItemInserted! mHandler.post(new Runnable() { @Override @@ -1381,14 +1399,6 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List " 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; -// } if (!autoMap) { throw new IllegalStateException("AutoMap is not active: super() cannot be called."); } @@ -1411,6 +1421,8 @@ else if (elevation > 0)//Leave unaltered the default elevation } //Endless Scroll onLoadMore(position); + //Scroll Animation + animateView(holder, position); } /*------------------------*/ @@ -1431,6 +1443,7 @@ else if (elevation > 0)//Leave unaltered the default elevation */ public FlexibleAdapter setEndlessProgressItem(@NonNull T progressItem) { if (progressItem != null) { + if (DEBUG) Log.i(TAG, "Set progressItem=" + progressItem.getClass().getSimpleName()); setEndlessScrollThreshold(mEndlessScrollThreshold); progressItem.setEnabled(false); mProgressItem = progressItem; @@ -1449,8 +1462,10 @@ public FlexibleAdapter setEndlessProgressItem(@NonNull T progressItem) { * @see #setEndlessProgressItem(IFlexible) * @since 5.0.0-b6 */ + //TODO: Deprecation? use setProgressItem + setEndlessScrollListener public FlexibleAdapter setEndlessScrollListener(@Nullable EndlessScrollListener endlessScrollListener, @NonNull T progressItem) { + if (DEBUG) Log.i(TAG, "Set endlessScrollListener=" + endlessScrollListener.getClass().getSimpleName()); mEndlessScrollListener = endlessScrollListener; return setEndlessProgressItem(progressItem); } @@ -1470,6 +1485,7 @@ public FlexibleAdapter setEndlessScrollThreshold(@IntRange(from = 1) int thresho thresholdItems = thresholdItems * spanCount; } mEndlessScrollThreshold = thresholdItems; + if (DEBUG) Log.i(TAG, "Set endlessScrollThreshold=" + mEndlessScrollThreshold); return this; } @@ -1593,6 +1609,7 @@ public boolean isAutoCollapseOnExpand() { * @since 5.0.0-b1 */ public FlexibleAdapter setAutoCollapseOnExpand(boolean collapseOnExpand) { + if (DEBUG) Log.i(TAG, "Set autoCollapseOnExpand=" + collapseOnExpand); this.collapseOnExpand = collapseOnExpand; return this; } @@ -1616,6 +1633,7 @@ public boolean isAutoScrollOnExpand() { * @since 5.0.0-b1 */ public FlexibleAdapter setAutoScrollOnExpand(boolean scrollOnExpand) { + if (DEBUG) Log.i(TAG, "Set setAutoScrollOnExpand=" + scrollOnExpand); this.scrollOnExpand = scrollOnExpand; return this; } @@ -1670,6 +1688,7 @@ public int getMinCollapsibleLevel() { * @since 5.0.0-b6 */ public FlexibleAdapter setMinCollapsibleLevel(int minCollapsibleLevel) { + if (DEBUG) Log.i(TAG, "Set minCollapsibleLevel=" + minCollapsibleLevel); this.mMinCollapsibleLevel = minCollapsibleLevel; return this; } @@ -1910,7 +1929,7 @@ private int expand(int position, boolean expandAll, boolean init) { } } if (DEBUG) { - Log.i(TAG, (init ? "Initially expanded " : "Expanded ") + + Log.v(TAG, (init ? "Initially expanded " : "Expanded ") + subItemsCount + " subItems on position=" + position); } } @@ -2456,7 +2475,7 @@ 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); } @@ -2809,6 +2828,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; } @@ -2836,6 +2856,7 @@ 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; } @@ -2979,7 +3000,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(); } @@ -3003,7 +3024,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; @@ -3019,7 +3040,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) @@ -3038,7 +3059,7 @@ public List getDeletedChildren(IExpandable expandable) { * @since 5.0.0-b1 */ @NonNull - public List getCurrentChildren(@NonNull IExpandable expandable) { + public final List getCurrentChildren(@NonNull IExpandable expandable) { //Check item and subItems existence if (expandable == null || !hasSubItems(expandable)) return new ArrayList<>(); @@ -3111,6 +3132,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; } @@ -3129,6 +3151,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; } @@ -3373,6 +3396,7 @@ private void resetFilterFlags(List items) { * @since 5.0.0-b8 */ public FlexibleAdapter setAnimateToLimit(int limit) { + if (DEBUG) Log.i(TAG, "Set animateToLimit=" + limit); mAnimateToLimit = limit; return this; } @@ -3557,6 +3581,7 @@ public final ItemTouchHelperCallback getItemTouchHelperCallback() { * @since 5.0.0-rc1 */ public final FlexibleAdapter setItemTouchHelperCallback(ItemTouchHelperCallback itemTouchHelperCallback) { + if (DEBUG) Log.i(TAG, "Initialize custom ItemTouchHelperCallback"); mItemTouchHelperCallback = itemTouchHelperCallback; mItemTouchHelper = null; initializeItemTouchHelper(); @@ -3577,7 +3602,7 @@ public final boolean isLongPressDragEnabled() { } /** - * Enable the Drag on LongPress on the entire ViewHolder. + * 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)}.

    @@ -3589,6 +3614,7 @@ public final boolean isLongPressDragEnabled() { */ public final FlexibleAdapter setLongPressDragEnabled(boolean longPressDragEnabled) { initializeItemTouchHelper(); + if (DEBUG) Log.i(TAG, "Set longPressDragEnabled=" + longPressDragEnabled); mItemTouchHelperCallback.setLongPressDragEnabled(longPressDragEnabled); return this; } @@ -3602,11 +3628,11 @@ public final FlexibleAdapter setLongPressDragEnabled(boolean longPressDragEnable * @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 @@ -3614,7 +3640,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; } @@ -3640,6 +3668,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; @@ -3857,6 +3886,7 @@ private void initializeItemTouchHelper() { 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) { + if (DEBUG) Log.i(TAG, "Initialize default ItemTouchHelperCallback"); mItemTouchHelperCallback = new ItemTouchHelperCallback(this); } mItemTouchHelper = new ItemTouchHelper(mItemTouchHelperCallback); @@ -4430,7 +4460,7 @@ private void postUpdate(boolean 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 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..df57ce87 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java @@ -157,6 +157,7 @@ public RecyclerView getRecyclerView() { * @return the span count * @since 5.0.0-b7 */ + //TODO: Deprecated? move to Utils public static int getSpanCount(RecyclerView.LayoutManager layoutManager) { if (layoutManager instanceof GridLayoutManager) { return ((GridLayoutManager) layoutManager).getSpanCount(); @@ -180,12 +181,21 @@ public static int getSpanCount(RecyclerView.LayoutManager layoutManager) { * @since 2.0.0 */ public void setMode(@Mode int mode) { + if (DEBUG) Log.i(TAG, getModeName(mode) + " enabled"); if (mMode == MODE_SINGLE && mode == MODE_IDLE) clearSelection(); this.mMode = mode; mLastItemInActionMode = (mode == MODE_IDLE); } + private String getModeName(int mode) { + switch (mode) { + case 1: return "MODE_SINGLE"; + case 2: return "MODE_MULTI"; + default: return "MODE_IDLE"; + } + } + /** * The current selection mode of the Adapter. * diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/AnimatorHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/AnimatorHelper.java index 61967172..b82b6c08 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/AnimatorHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/AnimatorHelper.java @@ -36,10 +36,6 @@ public class AnimatorHelper { protected static final String TAG = AnimatorHelper.class.getSimpleName(); -// private enum AnimatorEnum { -// ALPHA, SLIDE_IN_LEFT, SLIDE_IN_RIGHT, SLIDE_IN_BOTTOM, SLIDE_IN_TOP, SCALE -// } - /*-----------*/ /* ANIMATORS */ /*-----------*/ diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ItemTouchHelperCallback.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ItemTouchHelperCallback.java index d0593b57..c4e3fb7c 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ItemTouchHelperCallback.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ItemTouchHelperCallback.java @@ -44,7 +44,7 @@ public class ItemTouchHelperCallback extends Callback { protected static final float ALPHA_FULL = 1.0f; protected AdapterCallback mItemTouchCallback; - protected boolean mIsLongPressDragEnabled = false, mIsSwipeEnabled = false; + protected boolean longPressDragEnabled = false, handleDragEnabled = false, swipeEnabled = false; protected long mSwipeAnimationDuration = 300L, mDragAnimationDuration = 400L; protected float mSwipeThreshold = 0.5f, mMoveThreshold = 0.5f; protected int mSwipeFlags = -1; @@ -60,16 +60,17 @@ public ItemTouchHelperCallback(AdapterCallback itemTouchCallback) { /*-----------------------*/ /* CONFIGURATION SETTERS */ /*-----------------------*/ + /* DRAG */ /** - * Enable / disable the drag operation with long press on the ViewHolder. + * Enable / Disable the drag operation with long press on the ViewHolder. *

    Default value is {@code false}.

    * * @param isLongPressDragEnabled true to enable, false to disable */ public void setLongPressDragEnabled(boolean isLongPressDragEnabled) { - this.mIsLongPressDragEnabled = isLongPressDragEnabled; + this.longPressDragEnabled = isLongPressDragEnabled; } /** @@ -77,7 +78,25 @@ public void setLongPressDragEnabled(boolean isLongPressDragEnabled) { */ @Override public boolean isLongPressDragEnabled() { - return mIsLongPressDragEnabled; + return longPressDragEnabled; + } + + /** + * @return true if handle drag is enabled, false otherwise + */ + public boolean isHandleDragEnabled() { + return handleDragEnabled; + } + + /** + * Enable / Disable the drag of the itemView with a handle view. + *

    Default value is {@code false}.

    + * + * @param handleDragEnabled true to activate, false to disable + * @since 5.0.0-b1 + */ + public void setHandleDragEnabled(boolean handleDragEnabled) { + this.handleDragEnabled = handleDragEnabled; } /** @@ -117,7 +136,7 @@ public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) { * @param isSwipeEnabled true to enable swipe, false to disable */ public void setSwipeEnabled(boolean isSwipeEnabled) { - this.mIsSwipeEnabled = isSwipeEnabled; + this.swipeEnabled = isSwipeEnabled; } /** @@ -125,7 +144,7 @@ public void setSwipeEnabled(boolean isSwipeEnabled) { */ @Override public boolean isItemViewSwipeEnabled() { - return mIsSwipeEnabled; + return swipeEnabled; } /** diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index 7e08a7e7..36e85e9a 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -40,7 +40,7 @@ */ public class StickyHeaderHelper extends OnScrollListener { - private static final String TAG = FlexibleAdapter.class.getSimpleName(); + private static final String TAG = StickyHeaderHelper.class.getSimpleName(); private FlexibleAdapter mAdapter; private RecyclerView mRecyclerView; From 56e14970a801284b306acd4a12fa5d730a77b746 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 18 Oct 2016 19:17:45 +0200 Subject: [PATCH 11/92] Resolves #198 - Added onPostFilter() and onPostUpdate() that allow to modify the list just after the Asynchronous filter and update. --- build.gradle | 4 +- flexible-adapter-app/build.gradle | 6 +-- .../flexibleadapter/ExampleAdapter.java | 14 +++--- .../flexibleadapter/OverallAdapter.java | 5 ++- .../fragments/FragmentHeadersSections.java | 2 +- .../davidea/flexibleadapter/FilterTest.java | 44 +++++++++++++++++++ .../flexibleadapter/HeadersSectionsTest.java | 4 +- .../flexibleadapter/ItemComparatorTest.java | 4 +- .../flexibleadapter/FlexibleAdapter.java | 39 ++++++++++++++-- 9 files changed, 101 insertions(+), 21 deletions(-) create mode 100644 flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/FilterTest.java diff --git a/build.gradle b/build.gradle index 4a6682df..8b52f81b 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ ext { //Support and Build tools version minSdk = 14 targetSdk = 24 - buildTools = "24.0.2" + buildTools = "24.0.3" supportLib = "24.2.1" //Support Libraries dependencies @@ -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) { diff --git a/flexible-adapter-app/build.gradle b/flexible-adapter-app/build.gradle index d1f833d8..78b6323b 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' 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 09cfc732..26a00148 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,6 +1,5 @@ package eu.davidea.samples.flexibleadapter; -import android.support.annotation.NonNull; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.StaggeredGridLayoutManager; @@ -42,8 +41,8 @@ public void updateDataSet(List items, boolean animate) { //Watch out! The original list must a copy super.updateDataSet(items, animate); - //Add example view - //showLayoutInfo(true); + //onPostUpdate() will be automatically called at the end of the Asynchronous update process. + // Manipulate the list inside that method only or you won't see the changes. } /* @@ -65,7 +64,10 @@ 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()))); + item.setSubtitle(mRecyclerView.getContext().getString( + R.string.columns, + String.valueOf(getSpanCount(mRecyclerView.getLayoutManager()))) + ); addItemWithDelay((getItem(0) instanceof ULSItem ? 1 : 0), item, 0L, (!(getItem(0) instanceof ULSItem) && scrollToPosition)); removeItemWithDelay(item, 4000L, true); @@ -89,10 +91,8 @@ public void addUserLearnedSelection(boolean scrollToPosition) { } @Override - public void filterItems(@NonNull List unfilteredItems) { - super.filterItems(unfilteredItems); + protected void onPostFilter() { addUserLearnedSelection(false); - showLayoutInfo(false); } /** 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..0d8bdca3 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 @@ -57,7 +57,10 @@ 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()))); + item.setSubtitle(mRecyclerView.getContext().getString( + R.string.columns, + String.valueOf(getSpanCount(mRecyclerView.getLayoutManager()))) + ); addItemWithDelay(0, item, 500L, scrollToPosition); removeItemWithDelay(item, 2000L, true); } 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 939249ec..b3dd5a16 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 @@ -113,7 +113,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); //Add sample HeaderView items on the top (not belongs to the library) - //mAdapter.addUserLearnedSelection(savedInstanceState == null); + mAdapter.addUserLearnedSelection(savedInstanceState == null); mAdapter.showLayoutInfo(savedInstanceState == null); } 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/FlexibleAdapter.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java index 4f43ada6..05b81d45 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -223,12 +223,12 @@ public FlexibleAdapter(@Nullable List items) { * @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} *
    * @since 5.0.0-b1 */ @@ -269,8 +269,12 @@ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners, bool * @return this Adapter, so the call can be chained * @since 5.0.0-b6 */ + //TODO: Rename to addListener? + @CallSuper public FlexibleAdapter initializeListeners(@Nullable Object listener) { - if (DEBUG) Log.i(TAG, "Initialize Class " + listener.getClass().getSimpleName() + " as:"); + if (DEBUG && listener != null) { + Log.i(TAG, "Initialize Class " + listener.getClass().getSimpleName() + " as:"); + } if (listener instanceof OnItemClickListener) { if (DEBUG) Log.i(TAG, "- OnItemClickListener"); mItemClickListener = (OnItemClickListener) listener; @@ -499,6 +503,7 @@ public void updateDataSet(List items) { * @param animate true to animate the changes, false for a quick refresh * @see #updateDataSet(List) * @see #setAnimateToLimit(int) + * @see #onPostUpdate() * @since 5.0.0-b7 Created *
    5.0.0-b8 Synchronization animations limit */ @@ -3165,6 +3170,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 @@ -3182,6 +3188,8 @@ public void filterItems(@NonNull List unfilteredItems, @IntRange(from = 0) lo * {@link #setSearchText(String)}.

    * Important notes: *
      + *
    1. NEW! The Filter is always executed in background, asynchronously. + * The method {@link #onPostFilter()} is called after the filter task is completed.
    2. *
    3. This method calls {@link #filterObject(IFlexible, String)}.
    4. *
    5. If search text is empty or null, the provided list is the current list.
    6. *
    7. Any pending deleted items are always filtered out, but if restored, they will be @@ -3189,13 +3197,14 @@ public void filterItems(@NonNull List unfilteredItems, @IntRange(from = 0) lo *
    8. NEW! Expandable items are picked up and displayed if at least a child is * collected by the current filter.
    9. *
    10. NEW! Items are animated thanks to {@link #animateTo(List, Payload)} BUT a limit - * of {@value mAnimateToLimit} (default) items is set. NOTE: you can change this 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.
    11. *
    * * @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 @@ -4463,20 +4472,44 @@ private void postUpdate(boolean init) { if (DEBUG) Log.w(TAG, "updateDataSet with notifyDataSetChanged!"); notifyDataSetChanged(); } + // Perform user code + onPostUpdate(); //Update empty view if (mUpdateListener != null) { mUpdateListener.onUpdateEmptyView(getItemCount()); } } + /** + * 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 if (headersShown && !hasSearchText()) { showAllHeadersWithReset(false); } + //Perform user code + onPostFilter(); //Call listener to update EmptyView, assuming the filter always made a change if (mUpdateListener != null) mUpdateListener.onUpdateEmptyView(getItemCount()); } + /** + * 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 + } + } \ No newline at end of file From 25d71d4be1634be85c9d96d73bdef31f2151bdd6 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 18 Oct 2016 19:42:16 +0200 Subject: [PATCH 12/92] DemoApp: Note for binding using Method B. --- .../samples/flexibleadapter/OverallAdapter.java | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 0d8bdca3..5ce5ffc1 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 @@ -97,6 +97,8 @@ 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 calls need to be called by the user, see bottom of this method! */ @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payload) { @@ -132,6 +134,14 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List if (item.getIcon() != null) { 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)); } } From 85ebb85b14d0979ab7c3c33cf97fd1ebfa5d5540 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 18 Oct 2016 19:45:57 +0200 Subject: [PATCH 13/92] DemoApp: Note for binding using Method B. --- .../java/eu/davidea/samples/flexibleadapter/OverallAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5ce5ffc1..700153f0 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 @@ -98,7 +98,7 @@ 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 calls need to be called by the user, see bottom of this method! + * 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) { From f886ff454b2593bee869923f38512d5936d1ed77 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 19 Oct 2016 22:48:19 +0200 Subject: [PATCH 14/92] DemoApp: Added example for Item Model Holder --- .../samples/flexibleadapter/MainActivity.java | 18 ++- .../fragments/FragmentHolderSections.java | 94 ++++++++++++ ...stractModelItem.java => AbstractItem.java} | 10 +- .../flexibleadapter/models/AbstractModel.java | 76 ++++++++++ .../models/ExpandableHeaderItem.java | 2 +- .../models/ExpandableLevel0Item.java | 2 +- .../models/ExpandableLevel1Item.java | 2 +- .../flexibleadapter/models/HeaderHolder.java | 99 +++++++++++++ .../flexibleadapter/models/HeaderModel.java | 15 ++ .../flexibleadapter/models/ItemHolder.java | 108 ++++++++++++++ .../flexibleadapter/models/ItemModel.java | 15 ++ .../flexibleadapter/models/LayoutItem.java | 2 +- .../flexibleadapter/models/SimpleItem.java | 10 +- .../flexibleadapter/models/SubItem.java | 2 +- .../flexibleadapter/models/ULSItem.java | 2 +- .../services/DatabaseService.java | 39 ++++- .../showcase/ExpandableHeaderItemExample.java | 135 ------------------ .../showcase/FlexibleItemHolderExample.java | 100 ------------- .../res/layout/recycler_holder_header.xml | 41 ++++++ .../main/res/layout/recycler_holder_item.xml | 42 ++++++ .../main/res/menu/activity_entry_drawer.xml | 1 - .../src/main/res/menu/menu_holders.xml | 29 ++++ 22 files changed, 582 insertions(+), 262 deletions(-) create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentHolderSections.java rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/{AbstractModelItem.java => AbstractItem.java} (83%) create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractModel.java create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderHolder.java create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderModel.java create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ItemHolder.java create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ItemModel.java delete mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/showcase/ExpandableHeaderItemExample.java delete mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/showcase/FlexibleItemHolderExample.java create mode 100644 flexible-adapter-app/src/main/res/layout/recycler_holder_header.xml create mode 100644 flexible-adapter-app/src/main/res/layout/recycler_holder_item.xml create mode 100644 flexible-adapter-app/src/main/res/menu/menu_holders.xml 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 9634a72e..17fc44b7 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,8 +2,8 @@ import android.app.SearchManager; import android.content.Context; -import android.graphics.Color; import android.content.Intent; +import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -61,12 +61,13 @@ 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.AbstractItem; import eu.davidea.samples.flexibleadapter.models.ExpandableItem; import eu.davidea.samples.flexibleadapter.models.HeaderItem; import eu.davidea.samples.flexibleadapter.models.OverallItem; @@ -347,6 +348,8 @@ 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(); @@ -637,8 +640,8 @@ public boolean onOptionsItemSelected(MenuItem item) { 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; @@ -783,7 +786,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); @@ -1028,8 +1032,8 @@ private void logOrphanHeaders() { } 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; 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..1a625a48 --- /dev/null +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentHolderSections.java @@ -0,0 +1,94 @@ +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.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) + .enableStickyHeaders(); + + 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); + } + + @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/models/AbstractModelItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractItem.java similarity index 83% 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/models/AbstractItem.java index 2478ad29..90535651 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/models/AbstractItem.java @@ -9,9 +9,9 @@ * 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 +public abstract class AbstractItem extends AbstractFlexibleItem implements Serializable { @@ -21,14 +21,14 @@ public abstract class AbstractModelItem private String title; private String subtitle; - 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; 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/ExpandableHeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableHeaderItem.java index 5b0a6bf3..f9c6918b 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/models/ExpandableHeaderItem.java @@ -24,7 +24,7 @@ * 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 { 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/models/ExpandableLevel0Item.java index d6200851..b5167a9a 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/models/ExpandableLevel0Item.java @@ -26,7 +26,7 @@ * 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; 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/models/ExpandableLevel1Item.java index cd1fbaf8..4a38e41d 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/models/ExpandableLevel1Item.java @@ -17,7 +17,7 @@ * It's important to note that, the ViewHolder must be specified in all <diamond> signature. */ public class ExpandableLevel1Item - extends AbstractModelItem + extends AbstractItem implements IExpandable { private static final long serialVersionUID = -1882711111814491060L; diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderHolder.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderHolder.java new file mode 100644 index 00000000..808dd1dd --- /dev/null +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderHolder.java @@ -0,0 +1,99 @@ +package eu.davidea.samples.flexibleadapter.models; + +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.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/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/ItemHolder.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ItemHolder.java new file mode 100644 index 00000000..7206e072 --- /dev/null +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ItemHolder.java @@ -0,0 +1,108 @@ +package eu.davidea.samples.flexibleadapter.models; + +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.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/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 index 691fe031..302541c7 100644 --- 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 @@ -24,7 +24,7 @@ * {@link eu.davidea.flexibleadapter.items.AbstractFlexibleItem} to benefit of the already * implemented methods (getter and setters).

    */ -public class LayoutItem extends AbstractModelItem { +public class LayoutItem extends AbstractItem { private static final long serialVersionUID = -5041296095060813327L; 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/models/SimpleItem.java index b7d9dcdb..cbb04a45 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/models/SimpleItem.java @@ -31,7 +31,7 @@ * {@link eu.davidea.flexibleadapter.items.AbstractFlexibleItem} to benefit of the already * implemented methods (getter and setters). */ -public class SimpleItem extends AbstractModelItem +public class SimpleItem extends AbstractItem implements ISectionable, IFilterable, Serializable { private static final long serialVersionUID = -6882745111884490060L; @@ -157,9 +157,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); 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/models/SubItem.java index a3f0f1ff..d324dbf1 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/models/SubItem.java @@ -25,7 +25,7 @@ * {@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; 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/models/ULSItem.java index ca18062d..9097b4f8 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/models/ULSItem.java @@ -24,7 +24,7 @@ * {@link eu.davidea.flexibleadapter.items.AbstractFlexibleItem} to benefit of the already * implemented methods (getter and setters).

    */ -public class ULSItem extends AbstractModelItem { +public class ULSItem extends AbstractItem { private static final long serialVersionUID = -5041296095060813327L; 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 26f3e489..01b25960 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,7 +17,7 @@ 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.AbstractItem; import eu.davidea.samples.flexibleadapter.models.AnimatorExpandableItem; import eu.davidea.samples.flexibleadapter.models.AnimatorSubItem; import eu.davidea.samples.flexibleadapter.models.ConfigurationItem; @@ -25,9 +25,13 @@ 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.HeaderHolder; import eu.davidea.samples.flexibleadapter.models.HeaderItem; +import eu.davidea.samples.flexibleadapter.models.HeaderModel; import eu.davidea.samples.flexibleadapter.models.InstagramHeaderItem; import eu.davidea.samples.flexibleadapter.models.InstagramItem; +import eu.davidea.samples.flexibleadapter.models.ItemHolder; +import eu.davidea.samples.flexibleadapter.models.ItemModel; import eu.davidea.samples.flexibleadapter.models.OverallItem; import eu.davidea.samples.flexibleadapter.models.SimpleItem; import eu.davidea.samples.flexibleadapter.models.StaggeredHeaderItem; @@ -116,8 +120,7 @@ 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)) @@ -207,6 +210,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. */ @@ -391,6 +409,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 */ /*-----------------------*/ @@ -428,7 +459,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 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 77340056..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.d(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/res/layout/recycler_holder_header.xml b/flexible-adapter-app/src/main/res/layout/recycler_holder_header.xml new file mode 100644 index 00000000..317d6aa9 --- /dev/null +++ b/flexible-adapter-app/src/main/res/layout/recycler_holder_header.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + \ 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/menu/activity_entry_drawer.xml b/flexible-adapter-app/src/main/res/menu/activity_entry_drawer.xml index dc50d67c..c7c218f7 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 From fdfb2543669047549147c424f0ce2ac8e315a6c2 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 19 Oct 2016 22:49:28 +0200 Subject: [PATCH 15/92] Update to Gradle build tools 2.2.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8b52f81b..9aea9bc3 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.2.1' 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" From 24590e9d759b71771542c779aad11a2dd0fedb24 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 22 Oct 2016 15:22:22 +0200 Subject: [PATCH 16/92] Update to Gradle build tools 2.2.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9aea9bc3..532dca91 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.1' + classpath 'com.android.tools.build:gradle:2.2.2' 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" From 2ddec09a2fb4102e7ead5605886c1cb7e3d8a9e4 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 22 Oct 2016 15:29:49 +0200 Subject: [PATCH 17/92] Resolved #210 - Problem with item content change animation Added DiffUtil and modified the demoApp to compare the 2 solutions implemented. --- .../samples/flexibleadapter/MainActivity.java | 27 +- .../fragments/FragmentAsyncFilter.java | 6 +- .../services/DatabaseConfiguration.java | 5 +- .../src/main/res/menu/menu_filter.xml | 5 + .../src/main/res/values/strings.xml | 1 + .../flexibleadapter/FlexibleAdapter.java | 296 ++++++++++++++---- 6 files changed, 279 insertions(+), 61 deletions(-) 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 17fc44b7..55a55eef 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 @@ -538,6 +538,11 @@ public boolean onPrepareOptionsMenu(Menu menu) { reverseMenuItem.setEnabled(mAdapter.isAnimationOnScrollingEnabled()); reverseMenuItem.setChecked(mAdapter.isAnimationOnReverseScrolling()); } + //DiffUtil? + MenuItem diffUtilItem = menu.findItem(R.id.action_diff_util); + if (diffUtilItem != null) { + diffUtilItem.setChecked(DatabaseConfiguration.animateWithDiffUtil); + } return super.onPrepareOptionsMenu(menu); } @@ -554,12 +559,12 @@ 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()) { @@ -571,6 +576,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); @@ -797,8 +814,10 @@ 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 " + size + " items in " : "Refreshed list in "); + message += mAdapter.getTime() + "ms"; + Snackbar.make(findViewById(R.id.main_view), message, Snackbar.LENGTH_SHORT).show(); } } 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 0a4bdba2..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); 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/res/menu/menu_filter.xml b/flexible-adapter-app/src/main/res/menu/menu_filter.xml index bdb733e7..1f84c5a4 100644 --- a/flexible-adapter-app/src/main/res/menu/menu_filter.xml +++ b/flexible-adapter-app/src/main/res/menu/menu_filter.xml @@ -10,4 +10,9 @@ android:animateLayoutChanges="true" app:actionViewClass="android.support.v7.widget.SearchView"/> + +
    \ No newline at end of file diff --git a/flexible-adapter-app/src/main/res/values/strings.xml b/flexible-adapter-app/src/main/res/values/strings.xml index 73ec6ce6..2a0a4f60 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 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 05b81d45..c5ca69fc 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -26,6 +26,7 @@ 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; @@ -114,11 +115,15 @@ public class FlexibleAdapter private List mItems, mTempItems; /** - * HashSet and AsyncTask objects, will increase performance in big list + * 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. @@ -1021,7 +1026,8 @@ public ViewGroup getStickySectionHeadersHolder() { * @since 5.0.0-rc1 */ public FlexibleAdapter setStickyHeaderContainer(@Nullable ViewGroup stickyContainer) { - if (DEBUG) Log.i(TAG, "Set stickyHeaderContainer=" + stickyContainer.getClass().getSimpleName()); + if (DEBUG) + Log.i(TAG, "Set stickyHeaderContainer=" + stickyContainer.getClass().getSimpleName()); this.mStickyContainer = stickyContainer; return this; } @@ -1470,7 +1476,8 @@ public FlexibleAdapter setEndlessProgressItem(@NonNull T progressItem) { //TODO: Deprecation? use setProgressItem + setEndlessScrollListener public FlexibleAdapter setEndlessScrollListener(@Nullable EndlessScrollListener endlessScrollListener, @NonNull T progressItem) { - if (DEBUG) Log.i(TAG, "Set endlessScrollListener=" + endlessScrollListener.getClass().getSimpleName()); + if (DEBUG) + Log.i(TAG, "Set endlessScrollListener=" + endlessScrollListener.getClass().getSimpleName()); mEndlessScrollListener = endlessScrollListener; return setEndlessProgressItem(progressItem); } @@ -3275,7 +3282,8 @@ private synchronized void filterItemsAsync(@NonNull List unfilteredItems) { //Animate search results only in case of new SearchText if (hasNewSearchText(mSearchText)) { mOldSearchText = mSearchText; - animateTo(filteredItems, Payload.FILTER); + animateDiff(filteredItems, Payload.FILTER); + //animateTo(filteredItems, Payload.FILTER); } } @@ -3410,13 +3418,68 @@ public FlexibleAdapter setAnimateToLimit(int limit) { return this; } + /*-------------------------*/ + /* ANIMATE CHANGES METHODS */ + /*-------------------------*/ + + /** + * @return true to calculate animation changes with DiffUtil, false to use default calculation. + * @see #setAnimateChangesWithDiffUtil(boolean) + */ + 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) + */ + 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) + */ + public FlexibleAdapter setDiffUtilCallback(DiffUtilCallback diffUtilCallback) { + this.diffUtilCallback = diffUtilCallback; + return this; + } + + 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 {@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) @@ -3454,23 +3517,28 @@ private synchronized void animateTo(@Nullable List newItems, Payload payloadC private void applyAndAnimateRemovals(List from, List newItems) { //Using Hash for performance mHashItems = new HashSet<>(newItems); - int out = 0; + 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 (!mHashItems.contains(item) && (!isHeader(item) || (isHeader(item) && headersShown))) { + boolean isHeader = isHeader(item); + if (!mHashItems.contains(item) && (!isHeader || (isHeader && headersShown))) { //if (DEBUG) Log.v(TAG, "calculateRemovals remove position=" + i + " item=" + item + " searchText=" + mSearchText); from.remove(i); mNotifications.add(new Notification(i, Notification.REMOVE)); out++; } else if (notifyChangeOfUnfilteredItems) { - from.set(i, item); + from.set(i, newItems.get(newItems.indexOf(item))); mNotifications.add(new Notification(i, Notification.CHANGE)); - //if (DEBUG) Log.v(TAG, "calculateRemovals keep position=" + i + " item=" + item + " searchText=" + mSearchText); + mod++; + //if (DEBUG) Log.v(TAG, "calculateAdditions keep position=" + i + " item=" + item + " searchText=" + mSearchText); } } mHashItems = null; - if (DEBUG) Log.v(TAG, "calculateRemovals total out=" + out); + if (DEBUG) { + Log.v(TAG, "calculateRemovals total out=" + out); + Log.v(TAG, "calculateModifications total mod=" + mod); + } } /** @@ -3527,37 +3595,68 @@ private void applyAndAnimateMovedItems(List from, List newItems) { } private synchronized void executeNotifications(Payload payloadChange) { - if (DEBUG) Log.i(TAG, "Performing " + mNotifications.size() + " notifications"); - mItems = mTempItems;// Update mItems in the UI Thread - setAnimate(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; + 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 + setAnimate(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; - mNotifications = 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) { + if (DEBUG) Log.i(TAG, "Initialize default ItemTouchHelperCallback"); + mItemTouchHelperCallback = new ItemTouchHelperCallback(this); + } + mItemTouchHelper = new ItemTouchHelper(mItemTouchHelperCallback); + mItemTouchHelper.attachToRecyclerView(mRecyclerView); + } + } + /** * Used by {@link FlexibleViewHolder#onTouch(View, MotionEvent)} * to start Drag or Swipe when HandleView is touched. @@ -3571,9 +3670,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() { @@ -3583,7 +3684,8 @@ public final ItemTouchHelperCallback getItemTouchHelperCallback() { /** * Sets a custom callback implementation for Item touch. - *

    Helper will be reinitialized.

    + *

    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 @@ -3604,6 +3706,7 @@ public final FlexibleAdapter setItemTouchHelperCallback(ItemTouchHelperCallback * * @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() { @@ -3629,11 +3732,14 @@ public final FlexibleAdapter setLongPressDragEnabled(boolean longPressDragEnable } /** - * 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() { @@ -3662,6 +3768,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() { @@ -3889,20 +3996,6 @@ public void onItemSwiped(int position, int 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."); - } - if (mItemTouchHelperCallback == null) { - if (DEBUG) Log.i(TAG, "Initialize default ItemTouchHelperCallback"); - mItemTouchHelperCallback = new ItemTouchHelperCallback(this); - } - mItemTouchHelper = new ItemTouchHelper(mItemTouchHelperCallback); - mItemTouchHelper.attachToRecyclerView(mRecyclerView); - } - } - /*------------------------*/ /* OTHERS PRIVATE METHODS */ /*------------------------*/ @@ -4419,10 +4512,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, Payload.CHANGE); + animateDiff(newItems, Payload.CHANGE); + //animateTo(newItems, Payload.CHANGE); if (DEBUG) Log.d(TAG, "doInBackground - ended UPDATE"); break; case FILTER: @@ -4436,7 +4531,7 @@ protected Void doInBackground(Void... params) { @Override protected void onPostExecute(Void result) { - if (mNotifications != null) { + if (diffResult != null || mNotifications != null) { //Execute post data switch (what) { case UPDATE: @@ -4512,4 +4607,97 @@ protected void onPostFilter() { //Dedicated for user implementation } + /** + * The following Lists are available as: + *

    - {@code protected List oldItems;} + *
    - {@code protected List newItems;} + */ + 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) { +// if (DEBUG) Log.w(TAG, "oldItemPosition=" + oldItemPosition + ", newItemPosition=" + 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 From 1d3fec3a4c310bf06f12a9786adc61da0ffd671d Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 22 Oct 2016 15:32:43 +0200 Subject: [PATCH 18/92] Resolved the high delay for scroll animation on the first visible item after rotation --- .../flexibleadapter/AnimatorAdapter.java | 87 +++++++++++-------- .../helpers/AnimatorHelper.java | 10 +-- 2 files changed, 55 insertions(+), 42 deletions(-) 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 55ea71c2..1761eb57 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -25,7 +25,6 @@ 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; @@ -341,17 +340,25 @@ private void cancelExistingAnimation(final int hashCode) { * @param holder the ViewHolder just bound * @param position the current item position */ - //FIXME: first completed visible item on rotation gets high delay protected void animateView(final RecyclerView.ViewHolder holder, final int position) { -// if (DEBUG) + //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; + } +// if (DEBUG) { // Log.v(TAG, "shouldAnimate=" + shouldAnimate // + " isFastScroll=" + isFastScroll // + " isNotified=" + mAnimatorNotifierObserver.isPositionNotified() // + " isReverseEnabled=" + isReverseEnabled // + " mLastAnimatedPosition=" + mLastAnimatedPosition // + (!isReverseEnabled ? " Pos>AniPos=" + (position > mLastAnimatedPosition) : "") +// + " mMaxChildViews=" + mMaxChildViews // ); - +// } if (holder instanceof FlexibleViewHolder && shouldAnimate && !isFastScroll && !mAnimatorNotifierObserver.isPositionNotified() && (isReverseEnabled || position > mLastAnimatedPosition || (position == 0 && mRecyclerView.getChildCount() == 0)) ) { @@ -363,7 +370,7 @@ protected void animateView(final RecyclerView.ViewHolder holder, final int posit //User animators List animators = new ArrayList<>(); FlexibleViewHolder flexibleViewHolder = (FlexibleViewHolder) holder; - flexibleViewHolder.scrollAnimators(animators, position, position > mLastAnimatedPosition); + flexibleViewHolder.scrollAnimators(animators, position, position >= mLastAnimatedPosition); //Execute the animations together AnimatorSet set = new AnimatorSet(); @@ -373,16 +380,11 @@ protected void animateView(final RecyclerView.ViewHolder holder, final int posit set.addListener(new HelperAnimatorListener(hashCode)); if (mEntryStep) { //Stop stepDelay when screen is filled - set.setStartDelay(calculateAnimationDelay2(position)); + set.setStartDelay(calculateAnimationDelay1(position)); } set.start(); mAnimators.put(hashCode, set); if (DEBUG) Log.v(TAG, "animateView Scroll animation on position " + position); - - //Animate only during initial loading? - if (onlyEntryAnimation && position >= mMaxChildViews) { - shouldAnimate = false; - } } mAnimatorNotifierObserver.clearNotified(); @@ -428,8 +430,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(calculateAnimationDelay1(position)); } set.start(); mAnimators.put(itemView.hashCode(), set); @@ -452,13 +453,15 @@ private long calculateAnimationDelay1(int position) { 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; + //Calculate visible items int visibleItems = lastVisiblePosition - firstVisiblePosition; // if (DEBUG) Log.v(TAG, "Position=" + position + @@ -469,13 +472,21 @@ private long calculateAnimationDelay1(int position) { // " ChildCount=" + mRecyclerView.getChildCount()); //Stop stepDelay when screen is filled - if (mLastAnimatedPosition > visibleItems || //Normal Forward scrolling - (firstVisiblePosition > 1 && firstVisiblePosition <= mMaxChildViews)) { //Reverse scrolling + if (position - 1 > visibleItems || //Normal Forward scrolling + (firstVisiblePosition > 1 && firstVisiblePosition <= mMaxChildViews) || //Reverse scrolling + (position > mMaxChildViews && firstVisiblePosition == -1 && mRecyclerView.getChildCount() == 0)) { //Reverse scrolling and click on FastScroller if (DEBUG) Log.v(TAG, "Reset AnimationDelay on position " + position); return 0L; } - - return mInitialDelay += mStepDelay; + long delay = mInitialDelay; + int numColumns = getSpanCount(mRecyclerView.getLayoutManager()); + if (numColumns > 1) { + delay += mStepDelay * (position % numColumns); + } else { + delay += position * mStepDelay; + } +// if (DEBUG) Log.v(TAG, "Delay=" + delay); + return delay; } /** @@ -485,25 +496,26 @@ private long calculateAnimationDelay1(int position) { private long calculateAnimationDelay2(int position) { long delay; int firstVisiblePosition = Utils.findFirstCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager()); - int lastVisiblePosition = Utils.findLastCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager()); + int lastVisiblePosition = Utils.findFirstCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager()); - if (mLastAnimatedPosition > lastVisiblePosition) - lastVisiblePosition = mLastAnimatedPosition; + //Fix for high delay on the first visible item on rotation +// if (firstVisiblePosition < 0 && position >= 0) +// firstVisiblePosition = position - 1; - int numberOfItemsOnScreen = lastVisiblePosition - firstVisiblePosition; - int numberOfAnimatedItems = position - 1; + //Last visible position is the last animated when initially loading + if (position - 1 > lastVisiblePosition) + lastVisiblePosition = position - 1; - //Save max child count reached - if (mMaxChildViews < mRecyclerView.getChildCount()) - mMaxChildViews = mRecyclerView.getChildCount(); + int visibleItems = lastVisiblePosition - firstVisiblePosition; + int numberOfAnimatedItems = position - 1; - 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 delay = mStepDelay; - if (numberOfItemsOnScreen <= 1) { + if (visibleItems <= 1) { //When RecyclerView is initially loading no items are present //Use InitialDelay only for the first item delay += mInitialDelay; @@ -511,8 +523,8 @@ private long calculateAnimationDelay2(int position) { //Reset InitialDelay only when first item is already animated mInitialDelay = 0L; } - if (mRecyclerView.getLayoutManager() instanceof GridLayoutManager) { - int numColumns = ((GridLayoutManager) mRecyclerView.getLayoutManager()).getSpanCount(); + int numColumns = getSpanCount(mRecyclerView.getLayoutManager()); + if (numColumns > 1) { delay = mInitialDelay + mStepDelay * (position % numColumns); } @@ -523,9 +535,10 @@ private long calculateAnimationDelay2(int position) { // 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; } diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/AnimatorHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/AnimatorHelper.java index b82b6c08..709fec20 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/AnimatorHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/AnimatorHelper.java @@ -67,7 +67,7 @@ public static void slideInFromLeftAnimator( RecyclerView recyclerView, @FloatRange(from = 0.0, to= 1.0) float percent) { alphaAnimator(animators, view, 0f); animators.add(ObjectAnimator.ofFloat(view, "translationX", -recyclerView.getLayoutManager().getWidth() * percent, 0)); - if (FlexibleAdapter.DEBUG) Log.v(TAG, "Added LEFT Animator"); + if (FlexibleAdapter.DEBUG) Log.v(TAG, " Added LEFT Animator"); } /** @@ -83,7 +83,7 @@ public static void slideInFromRightAnimator( RecyclerView recyclerView, @FloatRange(from = 0.0, to = 1.0) float percent) { alphaAnimator(animators, view, 0f); animators.add(ObjectAnimator.ofFloat(view, "translationX", recyclerView.getLayoutManager().getWidth() * percent, 0)); - if (FlexibleAdapter.DEBUG) Log.v(TAG, "Added RIGHT Animator"); + if (FlexibleAdapter.DEBUG) Log.v(TAG, " Added RIGHT Animator"); } /** @@ -98,7 +98,7 @@ public static void slideInFromTopAnimator( RecyclerView recyclerView) { alphaAnimator(animators, view, 0f); animators.add(ObjectAnimator.ofFloat(view, "translationY", -recyclerView.getMeasuredHeight() >> 1, 0)); - if (FlexibleAdapter.DEBUG) Log.v(TAG, "Added TOP Animator"); + if (FlexibleAdapter.DEBUG) Log.v(TAG, " Added TOP Animator"); } /** @@ -113,7 +113,7 @@ public static void slideInFromBottomAnimator( RecyclerView recyclerView) { alphaAnimator(animators, view, 0f); animators.add(ObjectAnimator.ofFloat(view, "translationY", recyclerView.getMeasuredHeight() >> 1, 0)); - if (FlexibleAdapter.DEBUG) Log.v(TAG, "Added BOTTOM Animator"); + if (FlexibleAdapter.DEBUG) Log.v(TAG, " Added BOTTOM Animator"); } /** @@ -129,7 +129,7 @@ public static void scaleAnimator( alphaAnimator(animators, view, 0f); animators.add(ObjectAnimator.ofFloat(view, "scaleX", scaleFrom, 1f)); animators.add(ObjectAnimator.ofFloat(view, "scaleY", scaleFrom, 1f)); - if (FlexibleAdapter.DEBUG) Log.v(TAG, "Added SCALE Animator"); + if (FlexibleAdapter.DEBUG) Log.v(TAG, " Added SCALE Animator"); } } \ No newline at end of file From 143049de74c6bfcf5c7a041a386b7b705eab739b Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 23 Oct 2016 13:47:57 +0200 Subject: [PATCH 19/92] Improved #210: Using hash instead of indexOf() when updating new items with same id --- .../samples/flexibleadapter/MainActivity.java | 5 +++-- .../fragments/FragmentSelectionModes.java | 3 ++- .../flexibleadapter/models/AbstractItem.java | 15 +++++++++++--- .../flexibleadapter/models/SimpleItem.java | 14 +++++++++---- .../services/DatabaseService.java | 13 ++++++++++++ .../flexibleadapter/FlexibleAdapter.java | 20 ++++++++++++++++--- 6 files changed, 57 insertions(+), 13 deletions(-) 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 55a55eef..7551054f 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 @@ -255,6 +255,7 @@ private void initializeSwipeToRefresh() { @Override public void onRefresh() { //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 @@ -815,8 +816,8 @@ public void onUpdateEmptyView(int size) { fastScroller.setVisibility(View.GONE); } if (mAdapter != null) { - String message = (mAdapter.hasSearchText() ? "Filtered " + size + " items in " : "Refreshed list in "); - message += mAdapter.getTime() + "ms"; + 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(); } } 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 7ed88117..d2a13175 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 @@ -95,7 +95,8 @@ 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); + mAdapter.setNotifyChangeOfUnfilteredItems(true)//This will rebind new item when refreshed + .setMode(SelectableAdapter.MODE_SINGLE); //Experimenting NEW features (v5.0.0) mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view); diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractItem.java index 90535651..4443e051 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractItem.java @@ -17,9 +17,10 @@ public abstract class AbstractItem private static final long serialVersionUID = -6882745111884490060L; - private String id; - private String title; - private String subtitle; + protected String id; + protected String title; + protected String subtitle = ""; + protected int updates; public AbstractItem(String id) { this.id = id; @@ -67,6 +68,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/SimpleItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/SimpleItem.java index cbb04a45..cb79d1f7 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/models/SimpleItem.java @@ -52,6 +52,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; @@ -77,11 +84,10 @@ public ParentViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater 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(adapter.getCurrentChildren((IExpandable) this).size() + " subItems" + + (getHeader() != null ? " - " + getHeader().getId() : "") + + (getUpdates() > 0 ? " - u" + getUpdates() : "")); } - setSubtitle(getSubtitle() + (getHeader() != null ? " - " + getHeader().getId() : "")); Context context = holder.itemView.getContext(); int defColorAccent = context.getResources().getColor(R.color.colorAccent_light); 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 01b25960..8e434149 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 @@ -559,6 +559,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 item : mItems) { + if (item instanceof SimpleItem) { + SimpleItem simpleItem = (SimpleItem) item; + simpleItem.increaseUpdates(); + } + } + } + /** * A simple item comparator by Id. */ 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 c5ca69fc..d3c85eaa 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -44,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; @@ -3515,20 +3516,33 @@ private synchronized void animateTo(@Nullable List newItems, Payload payloadC * @since 5.0.0-b1 */ private void applyAndAnimateRemovals(List from, List newItems) { + //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); - boolean isHeader = isHeader(item); - if (!mHashItems.contains(item) && (!isHeader || (isHeader && headersShown))) { + if (!mHashItems.contains(item)) { //if (DEBUG) Log.v(TAG, "calculateRemovals remove position=" + i + " item=" + item + " searchText=" + mSearchText); from.remove(i); mNotifications.add(new Notification(i, Notification.REMOVE)); out++; } else if (notifyChangeOfUnfilteredItems) { - from.set(i, newItems.get(newItems.indexOf(item))); + 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); From 6e6dd30b4a1d3565d6506509b9446f2fed7c5588 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Thu, 3 Nov 2016 15:02:35 +0100 Subject: [PATCH 20/92] Refactored packages for Items/Models/Holders --- .../flexibleadapter/ExampleAdapter.java | 4 +- .../samples/flexibleadapter/MainActivity.java | 15 ++++---- .../flexibleadapter/OverallAdapter.java | 4 +- .../flexibleadapter/ViewPagerActivity.java | 1 + .../fragments/FragmentEndlessScrolling.java | 2 +- .../fragments/FragmentHeadersSections.java | 2 +- .../fragments/FragmentInstagramHeaders.java | 2 +- .../fragments/FragmentStaggeredLayout.java | 6 +-- .../fragments/FragmentViewPager.java | 2 +- .../{models => holders}/HeaderHolder.java | 3 +- .../{models => holders}/ItemHolder.java | 3 +- .../{models => items}/AbstractItem.java | 2 +- .../AnimatorExpandableItem.java | 4 +- .../{models => items}/AnimatorSubItem.java | 2 +- .../{models => items}/ConfigurationItem.java | 2 +- .../ExpandableHeaderItem.java | 4 +- .../{models => items}/ExpandableItem.java | 2 +- .../ExpandableLevel0Item.java | 4 +- .../ExpandableLevel1Item.java | 2 +- .../{models => items}/HeaderItem.java | 2 +- .../InstagramHeaderItem.java | 2 +- .../{models => items}/InstagramItem.java | 2 +- .../{models => items}/LayoutItem.java | 2 +- .../{models => items}/OverallItem.java | 2 +- .../{models => items}/ProgressItem.java | 4 +- .../{models => items}/SimpleItem.java | 2 +- .../StaggeredHeaderItem.java | 2 +- .../{models => items}/StaggeredItem.java | 2 +- .../StaggeredItemStatus.java | 2 +- .../{models => items}/SubItem.java | 2 +- .../{models => items}/ULSItem.java | 2 +- .../services/DatabaseService.java | 38 +++++++++---------- .../{ => views}/HeaderView.java | 3 +- .../{ => views}/ProgressBar.java | 4 +- .../src/main/res/layout/progress_bar.xml | 2 +- .../src/main/res/layout/toolbar_titles.xml | 4 +- 36 files changed, 75 insertions(+), 68 deletions(-) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => holders}/HeaderHolder.java (95%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => holders}/ItemHolder.java (96%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/AbstractItem.java (97%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/AnimatorExpandableItem.java (97%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/AnimatorSubItem.java (99%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/ConfigurationItem.java (99%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/ExpandableHeaderItem.java (97%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/ExpandableItem.java (97%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/ExpandableLevel0Item.java (97%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/ExpandableLevel1Item.java (98%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/HeaderItem.java (98%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/InstagramHeaderItem.java (98%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/InstagramItem.java (98%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/LayoutItem.java (98%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/OverallItem.java (98%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/ProgressItem.java (92%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/SimpleItem.java (99%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/StaggeredHeaderItem.java (97%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/StaggeredItem.java (99%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/StaggeredItemStatus.java (94%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/SubItem.java (98%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{models => items}/ULSItem.java (98%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{ => views}/HeaderView.java (93%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/{ => views}/ProgressBar.java (98%) 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 26a00148..2e297ca1 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 @@ -7,8 +7,8 @@ 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.samples.flexibleadapter.items.LayoutItem; +import eu.davidea.samples.flexibleadapter.items.ULSItem; import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration; /** 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 7551054f..f4673894 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 @@ -67,16 +67,17 @@ 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.AbstractItem; -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; 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 700153f0..d9a80c37 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 @@ -13,8 +13,8 @@ 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.samples.flexibleadapter.items.LayoutItem; +import eu.davidea.samples.flexibleadapter.items.OverallItem; import eu.davidea.samples.flexibleadapter.services.DatabaseService; import eu.davidea.utils.Utils; 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 index d8c8a523..955a7975 100644 --- 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 @@ -16,6 +16,7 @@ import android.view.View; import eu.davidea.samples.flexibleadapter.fragments.FragmentViewPager; +import eu.davidea.samples.flexibleadapter.views.HeaderView; public class ViewPagerActivity extends AppCompatActivity { 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 f04e0582..bf0c003e 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 @@ -25,7 +25,7 @@ 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.items.ProgressItem; import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration; import eu.davidea.samples.flexibleadapter.services.DatabaseService; import eu.davidea.utils.Utils; 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 b3dd5a16..db5ea6a0 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,7 @@ 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.services.DatabaseConfiguration; import eu.davidea.samples.flexibleadapter.services.DatabaseService; import eu.davidea.utils.Utils; 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..11636ccb 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 @@ -19,7 +19,7 @@ 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; /** 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..8afa3cde 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 @@ -19,9 +19,9 @@ 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.StaggeredHeaderItem; +import eu.davidea.samples.flexibleadapter.items.StaggeredItem; +import eu.davidea.samples.flexibleadapter.items.StaggeredItemStatus; import eu.davidea.samples.flexibleadapter.services.DatabaseService; /** 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 index 152a743c..df929704 100644 --- 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 @@ -21,7 +21,7 @@ import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.flipview.FlipView; import eu.davidea.samples.flexibleadapter.R; -import eu.davidea.samples.flexibleadapter.models.HeaderItem; +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; diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderHolder.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/HeaderHolder.java similarity index 95% rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderHolder.java rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/HeaderHolder.java index 808dd1dd..61eee3ab 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderHolder.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/HeaderHolder.java @@ -1,4 +1,4 @@ -package eu.davidea.samples.flexibleadapter.models; +package eu.davidea.samples.flexibleadapter.holders; import android.view.LayoutInflater; import android.view.View; @@ -14,6 +14,7 @@ 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; /** diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ItemHolder.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/ItemHolder.java similarity index 96% rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ItemHolder.java rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/ItemHolder.java index 7206e072..8af3ff68 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ItemHolder.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/ItemHolder.java @@ -1,4 +1,4 @@ -package eu.davidea.samples.flexibleadapter.models; +package eu.davidea.samples.flexibleadapter.holders; import android.view.LayoutInflater; import android.view.View; @@ -14,6 +14,7 @@ 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; /** diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java similarity index 97% rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractItem.java rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java index 4443e051..827c3b4e 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java @@ -1,4 +1,4 @@ -package eu.davidea.samples.flexibleadapter.models; +package eu.davidea.samples.flexibleadapter.items; import java.io.Serializable; 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 97% 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 fa2c95bb..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; 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 99% 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..63f9553b 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; 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 97% 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 f9c6918b..67c391b9 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; @@ -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.ExpandableHeaderItem.ExpandableHeaderViewHolder; +import eu.davidea.samples.flexibleadapter.items.ExpandableHeaderItem.ExpandableHeaderViewHolder; import eu.davidea.viewholders.ExpandableViewHolder; /** 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/items/ExpandableItem.java similarity index 97% rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableItem.java rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableItem.java index 0e947a84..7e6d775f 100644 --- 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/items/ExpandableItem.java @@ -1,4 +1,4 @@ -package eu.davidea.samples.flexibleadapter.models; +package eu.davidea.samples.flexibleadapter.items; import java.util.ArrayList; import java.util.List; 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 97% 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 b5167a9a..12c6ee2d 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; 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 98% 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 4a38e41d..4be91d22 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,4 +1,4 @@ -package eu.davidea.samples.flexibleadapter.models; +package eu.davidea.samples.flexibleadapter.items; import android.util.Log; import android.view.LayoutInflater; 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 98% 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 73f0148b..ceb8c86a 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; 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 98% 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 26e5867d..6030cff9 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; 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 98% 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..1d87e01e 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; 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/items/LayoutItem.java similarity index 98% rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/LayoutItem.java rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/LayoutItem.java index 302541c7..5d3fd875 100644 --- 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/items/LayoutItem.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; 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/models/ProgressItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ProgressItem.java similarity index 92% rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ProgressItem.java rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ProgressItem.java index fa7aeb1c..ccec60fd 100644 --- 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/items/ProgressItem.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; @@ -13,7 +13,7 @@ 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.samples.flexibleadapter.items.ProgressItem.ProgressViewHolder; import eu.davidea.viewholders.FlexibleViewHolder; /** 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 99% 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 cb79d1f7..e45d17bc 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,4 +1,4 @@ -package eu.davidea.samples.flexibleadapter.models; +package eu.davidea.samples.flexibleadapter.items; import android.animation.Animator; import android.content.Context; 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 97% 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 e645d16f..5bec4530 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; 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 99% 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..46de2b25 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; 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 98% 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 d324dbf1..8e802f9c 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; 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/ULSItem.java similarity index 98% 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/ULSItem.java index 9097b4f8..fbf9a0e8 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/ULSItem.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; 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 8e434149..dbf31cf4 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,27 +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.AbstractItem; -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.HeaderHolder; -import eu.davidea.samples.flexibleadapter.models.HeaderItem; +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.models.InstagramHeaderItem; -import eu.davidea.samples.flexibleadapter.models.InstagramItem; -import eu.davidea.samples.flexibleadapter.models.ItemHolder; +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.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.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. 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/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 @@ - - - \ No newline at end of file + \ No newline at end of file From 30cf1dbc93be57dca04cdfcb1530846aec3d87ab Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 12 Nov 2016 20:46:55 +0100 Subject: [PATCH 21/92] Added new methods clear(), clearAllBut(), addItem(item). Fixed collapsing bug for a parent with selected sub items. Reviewed how the internal Handler can be overridden: new internal class HandlerCallback has been created. Reviewed javaDoc. --- .../flexibleadapter/ExampleAdapter.java | 39 ++ .../flexibleadapter/AnimatorAdapter.java | 1 + .../flexibleadapter/FlexibleAdapter.java | 458 ++++++++++-------- .../flexibleadapter/SelectableAdapter.java | 11 - 4 files changed, 304 insertions(+), 205 deletions(-) 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 2e297ca1..abe03452 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,5 +1,8 @@ package eu.davidea.samples.flexibleadapter; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.StaggeredGridLayoutManager; @@ -30,6 +33,10 @@ public class ExampleAdapter extends FlexibleAdapter { public ExampleAdapter(List items, Object listeners) { //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 @@ -145,4 +152,36 @@ public String onCreateBubbleText(int 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 and 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 + case 0://async updateDataSet + case 1://async filterItems + case 2://confirm delete + case 8://onLoadMore remove progress item + return done; + + //free to use + case 10: + case 11: + return true; + } + return false; + } + } + } \ No newline at end of file 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 1761eb57..2b5cd1b3 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -52,6 +52,7 @@ * @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 { 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 d3c85eaa..43dcc26a 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -61,19 +61,18 @@ import eu.davidea.viewholders.FlexibleViewHolder; /** - * 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
    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 @@ -90,7 +89,7 @@ *
    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) */ @SuppressWarnings({"Range", "unused", "unchecked", "ConstantConditions", "SuspiciousMethodCalls", "WeakerAccess"}) public class FlexibleAdapter @@ -103,21 +102,11 @@ public class FlexibleAdapter private static final String EXTRA_HEADERS = TAG + "_headersShown"; 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; - /** - * The main container for ALL items. - */ + /* The main container for ALL items */ private List mItems, mTempItems; - /** - * HashSet, AsyncTask and DiffUtil objects, will increase performance in big list - */ + /* HashSet, AsyncTask and DiffUtil objects, will increase performance in big list */ private Set mHashItems; private List mNotifications; private FilterAsyncTask mFilterAsyncTask; @@ -126,36 +115,9 @@ public class FlexibleAdapter 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.

    - * 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; - } - 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) */ public static final long UNDO_TIMEOUT = 5000L; @@ -213,6 +175,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) { @@ -221,10 +185,10 @@ 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: @@ -236,6 +200,9 @@ public FlexibleAdapter(@Nullable List items) { *
  • {@link OnStickyHeaderChangeListener} *
  • {@link OnUpdateListener} * + * @see #FlexibleAdapter(List) + * @see #FlexibleAdapter(List, Object, boolean) + * @see #initializeListeners(Object) * @since 5.0.0-b1 */ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners) { @@ -245,12 +212,15 @@ 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 #initializeListeners(Object) * @since 5.0.0-b8 */ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners, boolean stableIds) { @@ -342,8 +312,7 @@ 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 @@ -372,6 +341,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 @@ -396,7 +366,7 @@ 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 */ @Override @@ -433,13 +403,13 @@ 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 @@ -485,7 +455,8 @@ public boolean isAnyChildSelected() { /** * Convenience method of {@link #updateDataSet(List, boolean)}. - *

    In this case changes will NOT be animated: {@link #notifyDataSetChanged()} will be invoked.

    + *

    In this case changes will NOT be animated: Warning! + * {@link #notifyDataSetChanged()} will be invoked.

    * * @param items the new data set * @see #updateDataSet(List, boolean) @@ -497,16 +468,19 @@ 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! + *

    + * Note: The following methods will be also called at the end of the operation: + *

    1. {@link #expandItemsAtStartUp()}
    2. + *
    3. {@link #showAllHeaders()} if headers are shown
    4. + *
    5. {@link #onPostUpdate()}
    6. + *
    7. {@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() @@ -526,11 +500,11 @@ public void updateDataSet(@Nullable List items, boolean animate) { } /** - * 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) { @@ -550,7 +524,7 @@ public long getItemId(int position) { } /** - * This cannot be overridden since the selection relies on it. + * This method cannot be overridden since the selection relies on it. * * @return the total number of the items currently displayed by the adapter * @see #getItemCountOfTypes(Integer...) @@ -593,9 +567,7 @@ public int getItemCountOfTypesUntil(@IntRange(from = 0) int position, Integer... 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; @@ -615,7 +587,7 @@ public boolean isEmpty() { } /** - * Retrieve the global position of the Item in the Adapter list. + * Retrieve the global position of the item in the Adapter list. * * @param item the item to find * @return the global position in the Adapter if found, -1 otherwise @@ -638,9 +610,9 @@ public boolean contains(@NonNull T 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 @@ -712,11 +684,11 @@ public FlexibleAdapter setRemoveOrphanHeaders(boolean removeOrphanHeaders) { } /** - * 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 */ @@ -793,7 +765,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) { @@ -801,7 +773,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 @@ -814,7 +786,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 @@ -825,7 +797,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 @@ -889,10 +861,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 @@ -908,10 +880,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 @@ -934,7 +907,7 @@ 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 @@ -1042,7 +1015,7 @@ public FlexibleAdapter setStickyHeaderContainer(@Nullable ViewGroup stickyContai * 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).

    + *

    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 @@ -1060,8 +1033,8 @@ public FlexibleAdapter setDisplayHeadersAtStartUp(boolean displayHeaders) { /** * 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. + * startup. + *
    To display headers at startup please use {@code setDisplayHeadersAtStartUp()} instead. *

    Note: Headers can only be shown or hidden all together.

    * * @see #hideAllHeaders() @@ -1331,13 +1304,13 @@ private boolean isHeaderShared(IHeader header, int positionStart, int itemCount) /*---------------------*/ /** - * Returns the ViewType for all Items depends by the current position. + * 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 */ @@ -1353,7 +1326,7 @@ public int getItemViewType(int position) { /** * 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). *

    {@inheritDoc} * * @return a new ViewHolder that holds a View of the given view type @@ -1504,8 +1477,8 @@ public FlexibleAdapter setEndlessScrollThreshold(@IntRange(from = 1) int thresho /** * 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 */ @@ -1606,7 +1579,7 @@ private void noMoreLoad() { /*--------------------*/ /** - * @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() { @@ -1614,8 +1587,8 @@ 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 @@ -1628,7 +1601,7 @@ public FlexibleAdapter setAutoCollapseOnExpand(boolean collapseOnExpand) { } /** - * @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() { @@ -1637,9 +1610,9 @@ 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 @@ -1685,7 +1658,7 @@ 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() { @@ -1719,7 +1692,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 @@ -1772,10 +1745,10 @@ public int getExpandablePositionOf(@NonNull T child) { } /** - * Provides the list where the child currently lays. + * Provides the full sub list where the child currently lays. * * @param child the child item - * @return the list of the child element, or a new list if item + * @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 #getRelativePositionOf(IFlexible) @@ -1789,11 +1762,11 @@ public List getSiblingsOf(@NonNull T child) { } /** - * Retrieves the position of a child in the list where it 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 position in the parent or -1 if, child is a parent itself or not found + * @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 @@ -1876,8 +1849,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 @@ -1986,8 +1959,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 @@ -2085,7 +2058,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 @@ -2099,7 +2072,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. * @@ -2112,7 +2085,7 @@ 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()) { + if (position < 0 || position >= getItemCount()) { Log.e(TAG, "Cannot updateItem on position out of OutOfBounds!"); return; } @@ -2126,7 +2099,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.

    @@ -2159,20 +2132,33 @@ public void run() { } /** - * 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 = getItemCount()}.

    + * + * @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 of the item to add, 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!"); @@ -2182,31 +2168,32 @@ public boolean addItem(@IntRange(from = 0) int position, @NonNull T 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.

    * - * @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; } + int initialCount = getItemCount(); + if (position < 0) { + Log.w(TAG, "addItems Position is negative! adding items to the end"); + position = initialCount; + } 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 { @@ -2230,7 +2217,7 @@ public boolean addItems(@IntRange(from = 0) int position, @NonNull List items /** * 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) @@ -2332,9 +2319,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 @@ -2400,14 +2387,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 @@ -2473,7 +2459,6 @@ public int addItemToSection(@NonNull ISectionable item, @NonNull IHeader header, else addItem(headerPosition + 1 + index, (T) item); } - //return the position return getGlobalPositionOf(item); } @@ -2481,6 +2466,46 @@ public int addItemToSection(@NonNull ISectionable item, @NonNull IHeader header, /* DELETE ITEMS METHODS */ /*----------------------*/ + /** + * Convenience method of {@link #removeRange(int, int, Object)} that removes all items, with + * {@code null} payload.

    + * + * @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"); + removeRange(0, getItemCount(), null); + } + + /** + * Clears the Adapter list retaining all items of the type provided as parameter. + *

    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<>(); + for (int i = 0; i < mItems.size(); 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)} @@ -2493,7 +2518,7 @@ public void removeItemWithDelay(@NonNull final T item, @IntRange(from = 0) long } /** - * 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.

    * @@ -2562,7 +2587,8 @@ public void removeItem(@IntRange(from = 0) int position, @Nullable Object payloa } /** - * 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...) @@ -2576,10 +2602,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 @@ -2633,8 +2659,11 @@ public int compare(Integer lhs, Integer rhs) { /** * Selectively removes all items of the type provided as parameter. + *

    This method is opposite of {@link #clearAllBut(Integer...)}.

    * * @param viewTypes the viewTypes to remove + * @see #clear() + * @see #clearAllBut(Integer...) * @see #removeItem(int, Object) * @see #removeItems(List) * @see #removeAllSelectedItems() @@ -2644,9 +2673,7 @@ 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))) + if (viewTypeList.contains(getItemViewType(i))) itemsToRemove.add(i); } this.removeItems(itemsToRemove); @@ -2656,6 +2683,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...) @@ -2669,8 +2698,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 @@ -2685,6 +2714,8 @@ 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) @@ -2783,9 +2814,11 @@ public void removeRange(@IntRange(from = 0) int positionStart, } /** - * 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) @@ -2798,13 +2831,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) @@ -2965,7 +3000,7 @@ public void restoreDeletedItems() { } /** - * 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.

    * @@ -2977,7 +3012,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 @@ -2987,7 +3022,7 @@ 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 @@ -3697,11 +3732,11 @@ public final ItemTouchHelperCallback getItemTouchHelperCallback() { } /** - * Sets a custom callback implementation for Item touch. + * 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 + * @param itemTouchHelperCallback the custom callback implementation for item touch * @return this Adapter, so the call can be chained * @since 5.0.0-rc1 */ @@ -4120,7 +4155,7 @@ private List getExpandableList(IExpandable expandable) { */ private boolean hasSubItemsSelected(int startPosition, List subItems) { for (T subItem : subItems) { - if (isSelected(startPosition + 1) || + if (isSelected(++startPosition) || (isExpandable(subItem) && hasSubItemsSelected(startPosition + 1, getExpandableList((IExpandable) subItem)))) return true; } @@ -4262,12 +4297,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); @@ -4279,9 +4316,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 @@ -4344,7 +4380,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: @@ -4622,9 +4658,45 @@ protected void onPostFilter() { } /** - * The following Lists are available as: - *

    - {@code protected List oldItems;} - *
    - {@code protected List newItems;} + * 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 = remove 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: //onLoadMore remove progress item + deleteProgressItem(); + return true; + } + return false; + } + } + + /** + * The old and new lists are available as: + *

    - {@code protected List oldItems;} + *
    - {@code protected List newItems;} */ public static class DiffUtilCallback extends DiffUtil.Callback { @@ -4651,7 +4723,7 @@ public final int getNewListSize() { } /** - * Called by the DiffUtil to decide whether two object represent the same Item. + * 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. * @@ -4661,7 +4733,6 @@ public final int getNewListSize() { */ @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { -// if (DEBUG) Log.w(TAG, "oldItemPosition=" + oldItemPosition + ", newItemPosition=" + newItemPosition); T oldItem = oldItems.get(oldItemPosition); T newItem = newItems.get(newItemPosition); return oldItem.equals(newItem); @@ -4704,7 +4775,6 @@ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { * * @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 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 df57ce87..6479fb2b 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java @@ -425,17 +425,6 @@ public List getSelectedPositions() { */ // public Set getSelectedPositionsAsSet() { // return mSelectedPositions; -// } - - /** - * Sorts and retrieves the list of selected items. - *

    To call once! Then call {@link #getSelectedPositions()}.

    - * - * @return Ordered list of selected items ids - */ -// public List getSortedSelectedPositions() { -// Collections.sort(mSelectedPositions); -// return mSelectedPositions; // } /*----------------*/ From bab7752732aea1137de9e764873065a8a6c0a853 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 12 Nov 2016 21:22:09 +0100 Subject: [PATCH 22/92] Optimized selection coherence for a parent with a selected sub item at any level. --- .../main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 43dcc26a..46465a41 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -389,7 +389,6 @@ public void toggleSelection(@IntRange(from = 0) int position) { super.toggleSelection(position); } } - //Reset flags if necessary, just to be sure if (getSelectedItemCount() == 0) { mSelectedLevel = -1; @@ -4156,7 +4155,7 @@ private List getExpandableList(IExpandable expandable) { private boolean hasSubItemsSelected(int startPosition, List subItems) { for (T subItem : subItems) { if (isSelected(++startPosition) || - (isExpandable(subItem) && hasSubItemsSelected(startPosition + 1, getExpandableList((IExpandable) subItem)))) + (isExpanded(subItem) && hasSubItemsSelected(startPosition, getExpandableList((IExpandable) subItem)))) return true; } return false; From 42a2e290dc0b653456dffa4b48c2675e7430becf Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 13 Nov 2016 01:24:19 +0100 Subject: [PATCH 23/92] Reviewed javaDoc. --- .../flexibleadapter/FlexibleAdapter.java | 36 +++++++++++-------- .../flexibleadapter/items/IFlexible.java | 20 +++++------ 2 files changed, 31 insertions(+), 25 deletions(-) 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 46465a41..d72c24ae 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -1050,7 +1050,6 @@ public void showAllHeaders() { */ private void showAllHeaders(boolean init) { if (init) { - if (DEBUG) Log.i(TAG, "showAllHeaders at startup"); //No notifyItemInserted! showAllHeadersWithReset(true); @@ -1304,7 +1303,7 @@ private boolean isHeaderShared(IHeader header, int positionStart, int itemCount) /** * 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 + *

    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).

    * @@ -1323,25 +1322,27 @@ public int getItemViewType(int position) { } /** - * 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). + *

    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 (item == null || !autoMap) { //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."); + 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()); @@ -1351,12 +1352,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 */ @@ -1369,8 +1373,8 @@ 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 @@ -1384,7 +1388,9 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List " layoutPosition=" + holder.getLayoutPosition()); } if (!autoMap) { - throw new IllegalStateException("AutoMap is not active: super() cannot be called."); + //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."); } //When user scrolls, this line binds the correct selection status holder.itemView.setActivated(isSelected(position)); diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/items/IFlexible.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/items/IFlexible.java index ebe9561d..4ebe0616 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/items/IFlexible.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/items/IFlexible.java @@ -107,11 +107,11 @@ public interface IFlexible { /*---------------------*/ /** - * Returns the layout resource Id to auto-map a specific ViewType on this Item. + * Returns the layout resource ID to AutoMap a specific ViewType on this Item. *

    NOTE: Should identify a resource Layout reference {@link android.R.layout} used - * by FlexibleAdapter to auto-map the ViewTypes.

    - * HELP: To know how to implement auto-map for ViewTypes please refer to the - * FlexibleAdapter WikiPage + * by FlexibleAdapter to AutoMap the ViewTypes.

    + * HELP: To know how to implement AutoMap for ViewTypes please refer to the + * FlexibleAdapter Wiki Page * on GitHub. * * @return Layout identifier @@ -120,9 +120,9 @@ public interface IFlexible { int getLayoutRes(); /** - * Delegates the creation of the ViewHolder to the user, if auto-map has been implemented. - *

    HELP: To know how to implement auto-map for ViewTypes please refer to the - * FlexibleAdapter WikiPage + * Delegates the creation of the ViewHolder to the user if AutoMap has been implemented. + *

    HELP: To know how to implement AutoMap for ViewTypes please refer to the + * FlexibleAdapter Wiki Page * on GitHub.

    * * @param adapter the Adapter instance extending {@link FlexibleAdapter} @@ -134,9 +134,9 @@ public interface IFlexible { VH createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent); /** - * Binds the data of this item to the given Layout, if auto-map has been implemented. - *

    HELP: To know how to implement auto-map for ViewTypes please refer to the - * FlexibleAdapter WikiPage + * Binds the data of this item to the given Layout if AutoMap has been implemented. + *

    HELP: To know how to implement AutoMap for ViewTypes please refer to the + * FlexibleAdapter Wiki Page * on GitHub.

    * How to use Payload, please refer to * {@link android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int, List)}. From b893c6daa5ac2b6cf8670e7ba9981ec50926efb3 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 15 Nov 2016 20:53:50 +0100 Subject: [PATCH 24/92] Fixed # 229 - Calling hideAllHeaders() might lead to NullPointerException (Fixed NPE and modified behaviour when hiding headers: now, it doesn't disable anymore the sticky flag) --- .../flexibleadapter/FlexibleAdapter.java | 8 +- .../helpers/StickyHeaderHelper.java | 96 ++++++++++--------- 2 files changed, 58 insertions(+), 46 deletions(-) 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 d72c24ae..daf8f801 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -281,7 +281,7 @@ public FlexibleAdapter initializeListeners(@Nullable Object listener) { /** * {@inheritDoc} - *

    Attaches the StickyHeaderHelper from the RecyclerView when necessary

    + *

    Attaches the StickyHeaderHelper from the RecyclerView if necessary.

    * * @since 5.0.0-b6 */ @@ -1151,7 +1151,11 @@ public void run() { position--; } headersShown = false; - setStickyHeaders(false); + // Clear the header currently sticky + if (mStickyHeaderHelper != null) { + mStickyHeaderHelper.clearHeaderWithAnimation(); + } + //setStickyHeaders(false); multiRange = false; } }); diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index 36e85e9a..6edd7e32 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -48,6 +48,7 @@ public class StickyHeaderHelper extends OnScrollListener { private FlexibleViewHolder mStickyHeaderViewHolder; private OnStickyHeaderChangeListener mStickyHeaderChangeListener; private int mHeaderPosition = RecyclerView.NO_POSITION; + private boolean displayWithAnimation = false; public StickyHeaderHelper(FlexibleAdapter adapter, @@ -73,40 +74,15 @@ public void attachToRecyclerView(RecyclerView parent) { mRecyclerView = parent; if (mRecyclerView != null) { mRecyclerView.addOnScrollListener(this); - mRecyclerView.post(new Runnable() { - @Override - public void run() { - initStickyHeadersHolder(); - } - }); + initStickyHeadersHolder(); } } public void detachFromRecyclerView(RecyclerView parent) { - if (mRecyclerView == parent) { - mRecyclerView.removeOnScrollListener(this); - mRecyclerView = null; - mStickyHolderLayout.animate().setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - clearHeader(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - mStickyHolderLayout.animate().alpha(0).start(); - if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout detached"); - } + mRecyclerView.removeOnScrollListener(this); + mRecyclerView = null; + clearHeaderWithAnimation(); + if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout detached"); } private void initStickyHeadersHolder() { @@ -138,9 +114,9 @@ private void onStickyHeaderChange(int sectionIndex) { } public void updateOrClearHeader(boolean updateHeaderContent) { - if (mStickyHolderLayout == null || mAdapter.hasSearchText() || - mRecyclerView == null || mRecyclerView.getChildCount() == 0) { - clearHeader(); + if (!mAdapter.areHeadersShown() || mAdapter.hasSearchText() || mAdapter.getItemCount() == 0 + || mStickyHolderLayout == null || !isAttachedToRecyclerView()) { + clearHeaderWithAnimation(); return; } int firstHeaderPosition = getHeaderPosition(RecyclerView.NO_POSITION); @@ -152,6 +128,12 @@ public void updateOrClearHeader(boolean updateHeaderContent) { } private void updateHeader(int headerPosition, boolean updateHeaderContent) { + // Animate if headers were hidden + if (displayWithAnimation) { + displayWithAnimation = false; + mStickyHolderLayout.setAlpha(0); + mStickyHolderLayout.animate().alpha(1).start(); + } // Check if there is a new header to be sticky if (mHeaderPosition != headerPosition) { mHeaderPosition = headerPosition; @@ -171,7 +153,7 @@ private void translateHeader() { int headerOffsetX = 0, headerOffsetY = 0; - //Search for the position where the next header item is found and take the new offset + //Search for the position where the next header item is found and translate the new offset for (int i = 0; i < mRecyclerView.getChildCount(); i++) { final View nextChild = mRecyclerView.getChildAt(i); if (nextChild != null) { @@ -182,12 +164,14 @@ private void translateHeader() { if (nextChild.getLeft() > 0) { int headerWidth = mStickyHolderLayout.getMeasuredWidth(); headerOffsetX = Math.min(nextChild.getLeft() - headerWidth, 0); + // TODO: AlphaListener = Math.abs((float)nextChild.getLeft()) / headerWidth); if (headerOffsetX < 0) break; } } else { if (nextChild.getTop() > 0) { int headerHeight = mStickyHolderLayout.getMeasuredHeight(); headerOffsetY = Math.min(nextChild.getTop() - headerHeight, 0); + // TODO: AlphaListener = Math.abs((float)nextChild.getTop()) / headerHeight); if (headerOffsetY < 0) break; } } @@ -229,6 +213,18 @@ private void ensureHeaderParent() { mStickyHolderLayout.addView(view); } + private void resetHeader(FlexibleViewHolder header) { + final View view = header.getContentView(); + removeViewFromParent(view); + //Reset transformation on removed header + view.setTranslationX(0); + view.setTranslationY(0); + mStickyHeaderViewHolder.itemView.setVisibility(View.VISIBLE); + if (!header.itemView.equals(view)) + ((ViewGroup) header.itemView).addView(view); + header.setIsRecyclable(true); + } + private void clearHeader() { if (mStickyHeaderViewHolder != null) { if (FlexibleAdapter.DEBUG) Log.d(TAG, "clearHeader"); @@ -240,16 +236,28 @@ private void clearHeader() { } } - private void resetHeader(FlexibleViewHolder header) { - final View view = header.getContentView(); - removeViewFromParent(view); - //Reset transformation on removed header - view.setTranslationX(0); - view.setTranslationY(0); - mStickyHeaderViewHolder.itemView.setVisibility(View.VISIBLE); - if (!header.itemView.equals(view)) - ((ViewGroup) header.itemView).addView(view); - header.setIsRecyclable(true); + public void clearHeaderWithAnimation() { + mStickyHolderLayout.animate().setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mStickyHolderLayout.animate().setListener(null); + displayWithAnimation = true; + clearHeader(); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + mStickyHolderLayout.animate().alpha(0).start(); } private static void removeViewFromParent(final View view) { From 6e6926ce35d6fcf3d8a3de099a15d6dd78e90458 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 15 Nov 2016 20:53:50 +0100 Subject: [PATCH 25/92] Fixed #229 - Calling hideAllHeaders() might lead to NullPointerException (Fixed NPE and modified behaviour when hiding headers: now, it doesn't disable anymore the sticky flag) --- .../flexibleadapter/FlexibleAdapter.java | 8 +- .../helpers/StickyHeaderHelper.java | 96 ++++++++++--------- 2 files changed, 58 insertions(+), 46 deletions(-) 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 d72c24ae..daf8f801 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -281,7 +281,7 @@ public FlexibleAdapter initializeListeners(@Nullable Object listener) { /** * {@inheritDoc} - *

    Attaches the StickyHeaderHelper from the RecyclerView when necessary

    + *

    Attaches the StickyHeaderHelper from the RecyclerView if necessary.

    * * @since 5.0.0-b6 */ @@ -1151,7 +1151,11 @@ public void run() { position--; } headersShown = false; - setStickyHeaders(false); + // Clear the header currently sticky + if (mStickyHeaderHelper != null) { + mStickyHeaderHelper.clearHeaderWithAnimation(); + } + //setStickyHeaders(false); multiRange = false; } }); diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index 36e85e9a..6edd7e32 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -48,6 +48,7 @@ public class StickyHeaderHelper extends OnScrollListener { private FlexibleViewHolder mStickyHeaderViewHolder; private OnStickyHeaderChangeListener mStickyHeaderChangeListener; private int mHeaderPosition = RecyclerView.NO_POSITION; + private boolean displayWithAnimation = false; public StickyHeaderHelper(FlexibleAdapter adapter, @@ -73,40 +74,15 @@ public void attachToRecyclerView(RecyclerView parent) { mRecyclerView = parent; if (mRecyclerView != null) { mRecyclerView.addOnScrollListener(this); - mRecyclerView.post(new Runnable() { - @Override - public void run() { - initStickyHeadersHolder(); - } - }); + initStickyHeadersHolder(); } } public void detachFromRecyclerView(RecyclerView parent) { - if (mRecyclerView == parent) { - mRecyclerView.removeOnScrollListener(this); - mRecyclerView = null; - mStickyHolderLayout.animate().setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - clearHeader(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - mStickyHolderLayout.animate().alpha(0).start(); - if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout detached"); - } + mRecyclerView.removeOnScrollListener(this); + mRecyclerView = null; + clearHeaderWithAnimation(); + if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout detached"); } private void initStickyHeadersHolder() { @@ -138,9 +114,9 @@ private void onStickyHeaderChange(int sectionIndex) { } public void updateOrClearHeader(boolean updateHeaderContent) { - if (mStickyHolderLayout == null || mAdapter.hasSearchText() || - mRecyclerView == null || mRecyclerView.getChildCount() == 0) { - clearHeader(); + if (!mAdapter.areHeadersShown() || mAdapter.hasSearchText() || mAdapter.getItemCount() == 0 + || mStickyHolderLayout == null || !isAttachedToRecyclerView()) { + clearHeaderWithAnimation(); return; } int firstHeaderPosition = getHeaderPosition(RecyclerView.NO_POSITION); @@ -152,6 +128,12 @@ public void updateOrClearHeader(boolean updateHeaderContent) { } private void updateHeader(int headerPosition, boolean updateHeaderContent) { + // Animate if headers were hidden + if (displayWithAnimation) { + displayWithAnimation = false; + mStickyHolderLayout.setAlpha(0); + mStickyHolderLayout.animate().alpha(1).start(); + } // Check if there is a new header to be sticky if (mHeaderPosition != headerPosition) { mHeaderPosition = headerPosition; @@ -171,7 +153,7 @@ private void translateHeader() { int headerOffsetX = 0, headerOffsetY = 0; - //Search for the position where the next header item is found and take the new offset + //Search for the position where the next header item is found and translate the new offset for (int i = 0; i < mRecyclerView.getChildCount(); i++) { final View nextChild = mRecyclerView.getChildAt(i); if (nextChild != null) { @@ -182,12 +164,14 @@ private void translateHeader() { if (nextChild.getLeft() > 0) { int headerWidth = mStickyHolderLayout.getMeasuredWidth(); headerOffsetX = Math.min(nextChild.getLeft() - headerWidth, 0); + // TODO: AlphaListener = Math.abs((float)nextChild.getLeft()) / headerWidth); if (headerOffsetX < 0) break; } } else { if (nextChild.getTop() > 0) { int headerHeight = mStickyHolderLayout.getMeasuredHeight(); headerOffsetY = Math.min(nextChild.getTop() - headerHeight, 0); + // TODO: AlphaListener = Math.abs((float)nextChild.getTop()) / headerHeight); if (headerOffsetY < 0) break; } } @@ -229,6 +213,18 @@ private void ensureHeaderParent() { mStickyHolderLayout.addView(view); } + private void resetHeader(FlexibleViewHolder header) { + final View view = header.getContentView(); + removeViewFromParent(view); + //Reset transformation on removed header + view.setTranslationX(0); + view.setTranslationY(0); + mStickyHeaderViewHolder.itemView.setVisibility(View.VISIBLE); + if (!header.itemView.equals(view)) + ((ViewGroup) header.itemView).addView(view); + header.setIsRecyclable(true); + } + private void clearHeader() { if (mStickyHeaderViewHolder != null) { if (FlexibleAdapter.DEBUG) Log.d(TAG, "clearHeader"); @@ -240,16 +236,28 @@ private void clearHeader() { } } - private void resetHeader(FlexibleViewHolder header) { - final View view = header.getContentView(); - removeViewFromParent(view); - //Reset transformation on removed header - view.setTranslationX(0); - view.setTranslationY(0); - mStickyHeaderViewHolder.itemView.setVisibility(View.VISIBLE); - if (!header.itemView.equals(view)) - ((ViewGroup) header.itemView).addView(view); - header.setIsRecyclable(true); + public void clearHeaderWithAnimation() { + mStickyHolderLayout.animate().setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mStickyHolderLayout.animate().setListener(null); + displayWithAnimation = true; + clearHeader(); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + mStickyHolderLayout.animate().alpha(0).start(); } private static void removeViewFromParent(final View view) { From 35aa4d3ab196b6242ba0f54e5cdfd257449fa741 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 16 Nov 2016 00:07:52 +0100 Subject: [PATCH 26/92] More comments for FragmentOverall.java --- .../fragments/FragmentOverall.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) 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..77e5de29 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 @@ -10,7 +10,6 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.animation.DecelerateInterpolator; import eu.davidea.flexibleadapter.SelectableAdapter; import eu.davidea.flexibleadapter.common.SmoothScrollGridLayoutManager; @@ -51,27 +50,36 @@ 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) + + // Experimenting NEW features (v5.0.0) mAdapter.setAnimationOnScrolling(true) .setAnimationOnReverseScrolling(true) - .setAnimationInterpolator(new DecelerateInterpolator()) + //.setAnimationInterpolator(new DecelerateInterpolator()) .setAnimationInitialDelay(500L) .setAnimationDelay(150L); + + // 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.setLayoutManager(createNewStaggeredGridLayoutManager()); mRecyclerView.setAdapter(mAdapter); mRecyclerView.setHasFixedSize(true);//Size of RV will not change + + // After Adapter is attached to RecyclerView mAdapter.setLongPressDragEnabled(true); mRecyclerView.postDelayed(new Runnable() { @Override @@ -86,7 +94,7 @@ public void run() { swipeRefreshLayout.setEnabled(true); mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); - //Add sample HeaderView items on the top (not belongs to the library) + // Add sample HeaderView items on the top (not belongs to the library) mAdapter.showLayoutInfo(savedInstanceState == null); } @@ -109,8 +117,8 @@ 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 integer to identify the ViewType, + // here, you should use them and not Layout integers switch (mAdapter.getItemViewType(position)) { case R.layout.recycler_layout_item: return mColumnCount; From 734b3f8735b3bcfc81b0c661357791233c80680c Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 20 Nov 2016 23:02:20 +0100 Subject: [PATCH 27/92] Resolved #231 - Now, if no layout is configured for sticky headers, IllegalStateException is raised. --- .../flexibleadapter/FlexibleAdapter.java | 22 +++++++++---------- .../helpers/StickyHeaderHelper.java | 11 +++------- .../viewholders/FlexibleViewHolder.java | 17 +++++++++----- 3 files changed, 24 insertions(+), 26 deletions(-) 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 daf8f801..97550f68 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -127,7 +127,7 @@ public class FlexibleAdapter /* Header/Section items */ private List mOrphanHeaders; - private boolean headersShown = false, headersSticky = false, recursive = false; + private boolean headersShown = false, recursive = false; private StickyHeaderHelper mStickyHeaderHelper; private ViewGroup mStickyContainer; @@ -302,7 +302,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); @@ -912,7 +912,7 @@ public boolean areHeadersShown() { * @since 5.0.0-b6 */ public boolean areHeadersSticky() { - return headersSticky; + return mStickyHeaderHelper != null; } /** @@ -947,17 +947,15 @@ private FlexibleAdapter setStickyHeaders(final boolean sticky) { 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) + if (mStickyHeaderHelper == null) { mStickyHeaderHelper = new StickyHeaderHelper(FlexibleAdapter.this, mStickyHeaderChangeListener); - if (!mStickyHeaderHelper.isAttachedToRecyclerView()) 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"); } @@ -998,8 +996,8 @@ public ViewGroup getStickySectionHeadersHolder() { * @param stickyContainer custom container for Sticky Headers * @since 5.0.0-rc1 */ - public FlexibleAdapter setStickyHeaderContainer(@Nullable ViewGroup stickyContainer) { - if (DEBUG) + public FlexibleAdapter setStickyHeaderContainer(@NonNull ViewGroup stickyContainer) { + if (DEBUG && stickyContainer != null) Log.i(TAG, "Set stickyHeaderContainer=" + stickyContainer.getClass().getSimpleName()); this.mStickyContainer = stickyContainer; return this; diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index 6edd7e32..68e71005 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -62,10 +62,6 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { updateOrClearHeader(false); } - public boolean isAttachedToRecyclerView() { - return mRecyclerView != null; - } - public void attachToRecyclerView(RecyclerView parent) { if (mRecyclerView != null) { mRecyclerView.removeOnScrollListener(this); @@ -78,7 +74,7 @@ public void attachToRecyclerView(RecyclerView parent) { } } - public void detachFromRecyclerView(RecyclerView parent) { + public void detachFromRecyclerView() { mRecyclerView.removeOnScrollListener(this); mRecyclerView = null; clearHeaderWithAnimation(); @@ -98,7 +94,7 @@ private void initStickyHeadersHolder() { mStickyHolderLayout.animate().alpha(1).start(); if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout initialized"); } else { - Log.w(TAG, "WARNING! ViewGroup for Sticky Headers unspecified! You must include @layout/sticky_header_layout or implement FlexibleAdapter.getStickySectionHeadersHolder() method"); + throw new IllegalStateException("ViewGroup for Sticky Headers unspecified! You must either include @layout/sticky_header_layout OR set a custom StickyHeaderContainer"); } } @@ -114,8 +110,7 @@ private void onStickyHeaderChange(int sectionIndex) { } public void updateOrClearHeader(boolean updateHeaderContent) { - if (!mAdapter.areHeadersShown() || mAdapter.hasSearchText() || mAdapter.getItemCount() == 0 - || mStickyHolderLayout == null || !isAttachedToRecyclerView()) { + if (!mAdapter.areHeadersShown() || mAdapter.hasSearchText() || mAdapter.getItemCount() == 0) { clearHeaderWithAnimation(); return; } diff --git a/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java b/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java index b18cbf57..b8ccfbeb 100644 --- a/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java +++ b/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java @@ -29,6 +29,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.SelectableAdapter; +import eu.davidea.flexibleadapter.helpers.AnimatorHelper; import eu.davidea.flexibleadapter.helpers.ItemTouchHelperCallback; import eu.davidea.flexibleadapter.items.IFlexible; @@ -226,7 +227,7 @@ public float getActivationElevation() { /** * Allows to activate the itemView when Swipe event occurs. *

    This method returns always false; Extend with "return true" to Not expand or collapse - * this ItemView onClick events.

    + * this itemView onClick events.

    * * @return always false, if not overridden * @since 5.0.0-b2 @@ -237,8 +238,8 @@ protected boolean shouldActivateViewWhileSwiping() { /** * Allows to add and keep item selection if ActionMode is active. - *

    This method returns always false; Extend with "return true" to add item to the ActionMode - * count.

    + *

    This method returns always false; Extend with "return true" to add the item to the + * ActionMode count.

    * * @return always false, if not overridden * @since 5.0.0-b2 @@ -256,12 +257,14 @@ protected boolean shouldAddSelectionInActionMode() { * actively scrolls the list (forward or backward). *

    Implement your logic for different animators based on position, selection and/or * direction.

    - * Create your {@link Animator}(s), then add it to the list of animators. + * Use can take one of the predefined Animator from {@link AnimatorHelper} or create your own + * {@link Animator}(s), then add it to the list of animators. * + * @param animators NonNull list of animators, which you should add new animators * @param position can be used to differentiate the Animators based on positions * @param isForward can be used to separate animation from top/bottom or from left/right scrolling * @since 5.0.0-b8 - * @see eu.davidea.flexibleadapter.helpers.AnimatorHelper + * @see AnimatorHelper */ public void scrollAnimators(@NonNull List animators, int position, boolean isForward) { //Free to implement @@ -274,7 +277,7 @@ public void scrollAnimators(@NonNull List animators, int position, boo /** * Here we handle the event of when the ItemTouchHelper first registers an item as being * moved or swiped. - *

    In this implementations, View activation is automatically handled in case of Drag: + *

    In this implementation, View activation is automatically handled in case of Drag: * The Item will be added to the selection list if not selected yet and mode MULTI is activated.

    * * @param position the position of the item touched @@ -357,6 +360,7 @@ public void onItemReleased(int position) { } /** + * @return the boolean value from the item flag, true to allow dragging * @since 5.0.0-b7 */ @Override @@ -366,6 +370,7 @@ public boolean isDraggable() { } /** + * @return the boolean value from the item flag, true to allow swiping * @since 5.0.0-b7 */ @Override From 4b6c34b79ae7a676c61e020fe2bc147e3b1d9281 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 21 Nov 2016 01:23:38 +0100 Subject: [PATCH 28/92] Fix for adjustSelection() when adding new items and some items are already selected --- .../java/eu/davidea/flexibleadapter/FlexibleAdapter.java | 9 +++++++++ 1 file changed, 9 insertions(+) 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 97550f68..918e1431 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -4201,6 +4201,15 @@ public boolean handleMessage(Message message) { private void adjustSelected(int startPosition, int itemCount) { List selectedPositions = getSelectedPositions(); boolean adjusted = false; + if (itemCount > 0) { + // Reverse sorting is necessary because using Set might remove duplicates during adjusting + Collections.sort(selectedPositions, new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + }); + } for (Integer position : selectedPositions) { if (position >= startPosition) { if (DEBUG) From 1307cc66b527918cf1174046420abd1ef075dc54 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 21 Nov 2016 01:23:53 +0100 Subject: [PATCH 29/92] DemoApp: delayed added items must ALREADY exist in the list before restoring the selection --- .../samples/flexibleadapter/MainActivity.java | 37 ++++++- .../fragments/FragmentSelectionModes.java | 96 ++++--------------- 2 files changed, 57 insertions(+), 76 deletions(-) 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 f4673894..5302524f 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 @@ -123,6 +123,11 @@ public class MainActivity extends AppCompatActivity implements * Bundle key representing the Active Fragment */ private static final String STATE_ACTIVE_FRAGMENT = "active_fragment"; + /** + * 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"; /** * FAB @@ -142,6 +147,12 @@ public class MainActivity extends AppCompatActivity implements private NavigationView mNavigationView; private AbstractFragment mFragment; private SearchView mSearchView; + + /** + * The current activated item position. + */ + private int mActivatedPosition = RecyclerView.NO_POSITION; + private final Handler mRefreshHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { public boolean handleMessage(Message message) { switch (message.what) { @@ -205,6 +216,9 @@ protected void onRestoreInstanceState(Bundle savedInstanceState) { //Selection mAdapter.onRestoreInstanceState(savedInstanceState); mActionModeHelper.restoreSelection(this); + //Previously serialized activated item position + if (savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) + setSelection(savedInstanceState.getInt(STATE_ACTIVATED_POSITION)); } } @@ -654,7 +668,6 @@ public boolean onOptionsItemSelected(MenuItem item) { /* DIALOG LISTENER IMPLEMENTATION (For the example of onItemClick) */ - @Override public void onTitleModified(int position, String newTitle) { AbstractFlexibleItem abstractItem = mAdapter.getItem(position); @@ -669,6 +682,27 @@ public void onTitleModified(int position, String newTitle) { mAdapter.updateItem(position, abstractItem, null); } + /* LAST SELECTED ITEM */ + + private void setActivatedPosition(int position) { + Log.d(TAG, "ItemList New mActivatedPosition=" + position); + mActivatedPosition = position; + } + + //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); + } + } + /* FLEXIBLE ADAPTER LISTENERS IMPLEMENTATION */ @Override @@ -683,6 +717,7 @@ public boolean onItemClick(int position) { //Action on elements are allowed if Mode is IDLE, otherwise selection has priority if (mAdapter.getMode() != SelectableAdapter.MODE_IDLE && mActionModeHelper != null) { + if (position != mActivatedPosition) setActivatedPosition(position); return mActionModeHelper.onClick(position); } else { //Notify the active callbacks or implement a custom action onClick 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 d2a13175..1cbbc4e5 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,22 @@ 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.ULSItem; +import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration; import eu.davidea.samples.flexibleadapter.services.DatabaseService; import eu.davidea.utils.Utils; @@ -27,17 +29,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. */ @@ -65,17 +60,6 @@ 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); @@ -92,9 +76,23 @@ public void onActivityCreated(Bundle savedInstanceState) { @SuppressWarnings({"ConstantConditions", "NullableProblems"}) private void initializeRecyclerView(Bundle savedInstanceState) { + //Get copy of the Database list + List items = DatabaseService.getInstance().getDatabaseList(); + + //TODO: find a solution for delayed items added after the restoring of the selected items + //The delayed items must ALREADY exist in the list before restoring the selection, otherwise + // the selection CANNOT be adjusted + if (savedInstanceState != null && !DatabaseConfiguration.userLearnedSelection) { + //Define Example View + final ULSItem item = new ULSItem("ULS"); + item.setTitle(getString(R.string.uls_title)); + item.setSubtitle(getString(R.string.uls_subtitle)); + items.add(0, item); + } + //Initialize Adapter and RecyclerView //ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()' - mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity()); + mAdapter = new ExampleAdapter(items, getActivity()); mAdapter.setNotifyChangeOfUnfilteredItems(true)//This will rebind new item when refreshed .setMode(SelectableAdapter.MODE_SINGLE); @@ -135,58 +133,6 @@ public void showNewLayoutInfo(MenuItem 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); - } - @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_selection_modes, menu); From 8e8962733a74e30362893d6e0031058359033e09 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 23 Nov 2016 01:30:28 +0100 Subject: [PATCH 30/92] Resolves #236 - Scrollable Headers and Footers items Adapted the code to support the new header and footer concept. --- .../flexibleadapter/AnimatorAdapter.java | 2 +- .../flexibleadapter/FlexibleAdapter.java | 468 +++++++++++++----- .../flexibleadapter/SelectableAdapter.java | 15 +- 3 files changed, 359 insertions(+), 126 deletions(-) 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 2b5cd1b3..2c56d221 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -381,7 +381,7 @@ protected void animateView(final RecyclerView.ViewHolder holder, final int posit set.addListener(new HelperAnimatorListener(hashCode)); if (mEntryStep) { //Stop stepDelay when screen is filled - set.setStartDelay(calculateAnimationDelay1(position)); + set.setStartDelay(calculateAnimationDelay2(position)); } set.start(); mAnimators.put(hashCode, set); 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 918e1431..a710b74c 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -102,6 +102,7 @@ public class FlexibleAdapter private static final String EXTRA_HEADERS = TAG + "_headersShown"; private static final String EXTRA_LEVEL = TAG + "_selectedLevel"; private static final String EXTRA_SEARCH = TAG + "_searchText"; + private static final String EXTRA_DELAYED_HEADERS = TAG + "_delayedHeaders"; /* The main container for ALL items */ private List mItems, mTempItems; @@ -119,13 +120,16 @@ public class FlexibleAdapter 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, recursive = false; private StickyHeaderHelper mStickyHeaderHelper; @@ -142,7 +146,8 @@ public class FlexibleAdapter 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 mMinCollapsibleLevel = 0, mSelectedLevel = -1; @@ -227,6 +232,8 @@ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners, bool super(stableIds); if (items == null) mItems = new ArrayList<>(); else mItems = items; + mScrollableHeaders = new ArrayList<>(); + mScrollableFooters = new ArrayList<>(); mRestoreList = new ArrayList<>(); mOrphanHeaders = new ArrayList<>(); @@ -274,7 +281,7 @@ public FlexibleAdapter initializeListeners(@Nullable Object listener) { if (listener instanceof OnUpdateListener) { if (DEBUG) Log.i(TAG, "- OnUpdateListener"); mUpdateListener = (OnUpdateListener) listener; - mUpdateListener.onUpdateEmptyView(getItemCount()); + mUpdateListener.onUpdateEmptyView(getItemCount(false)); } return this; } @@ -321,7 +328,7 @@ public FlexibleAdapter expandItemsAtStartUp() { int position = 0; setAnimate(true); multiRange = true; - while (position < mItems.size()) { + while (position < getItemCount()) { T item = getItem(position); if (isExpanded(item)) { expand(position, false, true); @@ -471,12 +478,12 @@ public void updateDataSet(List items) { * 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! - *

    + *

    Note: Scrollable Headers and Footers (if existent) will be restored in this call.

    * Note: The following methods will be also called at the end of the operation: *
    1. {@link #expandItemsAtStartUp()}
    2. *
    3. {@link #showAllHeaders()} if headers are shown
    4. *
    5. {@link #onPostUpdate()}
    6. - *
    7. {@link OnUpdateListener#onUpdateEmptyView(int)} if the listener is set

    + *
  • {@link OnUpdateListener#onUpdateEmptyView(int)} if the listener is set
  • * * @param items the new data set * @param animate true to animate the changes, false for an instant refresh @@ -488,12 +495,12 @@ public void updateDataSet(List items) { */ @CallSuper public void updateDataSet(@Nullable List items, boolean animate) { + 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 == null ? new ArrayList() : items); postUpdate(true); } } @@ -507,11 +514,13 @@ public void updateDataSet(@Nullable List items, boolean animate) { * @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 @@ -523,9 +532,14 @@ public long getItemId(int position) { } /** - * This method 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 #getItemCount(boolean)} 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 #getItemCount(boolean) * @see #getItemCountOfTypes(Integer...) * @see #getItemCountOfTypesUntil(int, Integer...) * @see #isEmpty() @@ -533,7 +547,28 @@ public long getItemId(int position) { */ @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. + * + * @param withHeadersFooters true to count also headers and footers, false to count only real items + * @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...) + * @see #getItemCountOfTypesUntil(int, Integer...) + * @since 5.0.0-rc1 + */ + public final int getItemCount(boolean withHeadersFooters) { + return withHeadersFooters ? getItemCount() : + getItemCount() - mScrollableHeaders.size() - mScrollableFooters.size(); } /** @@ -542,11 +577,12 @@ public final int getItemCount() { * @param viewTypes the viewTypes to count * @return number of the viewTypes counted * @see #getItemCount() + * @see #getItemCount(boolean) * @see #getItemCountOfTypesUntil(int, Integer...) * @see #isEmpty() * @since 5.0.0-b1 */ - public int getItemCountOfTypes(Integer... viewTypes) { + public final int getItemCountOfTypes(Integer... viewTypes) { return getItemCountOfTypesUntil(getItemCount(), viewTypes); } @@ -557,12 +593,13 @@ public int getItemCountOfTypes(Integer... viewTypes) { * @param position the position limit where to stop counting (included) * @param viewTypes the viewTypes to count * @see #getItemCount() + * @see #getItemCount(boolean) * @see #getItemCountOfTypes(Integer...) * @see #isEmpty() * @since 5.0.0-b5 */ //TODO: deprecation? - public int getItemCountOfTypesUntil(@IntRange(from = 0) int position, Integer... viewTypes) { + 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++) { @@ -586,14 +623,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 cannot be overridden. + *
    - This method is NOT suitable to call when removing items, if so, you should retrieve + * the global position with {@link #getGlobalPositionOf(IFlexible)}.

    + * + * @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; } /** @@ -604,7 +661,7 @@ 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); } /** @@ -652,6 +709,130 @@ public int calculatePositionFor(@NonNull Object item, @Nullable Comparator compa return Math.max(0, sortedList.indexOf(item)); } + /*------------------------------------*/ + /* SCROLLABLE HEADERS/FOOTERS METHODS */ + /*------------------------------------*/ + + public final List getScrollableHeaders() { + return Collections.unmodifiableList(mScrollableHeaders); + } + + public final List getScrollableFooters() { + return Collections.unmodifiableList(mScrollableFooters); + } + + public final boolean addScrollableHeader(@NonNull T headerItem) { + if (DEBUG) Log.d(TAG, "Add scrollable header " + headerItem); + if (!mScrollableHeaders.contains(headerItem) && addItem(0, headerItem)) { + headerItem.setSelectable(false); + return mScrollableHeaders.add(headerItem); + } + return false; + } + + public final boolean addScrollableFooter(@NonNull T footerItem) { + if (!mScrollableFooters.contains(footerItem) && addItem(getItemCount(), footerItem)) { + if (DEBUG) Log.d(TAG, "Add scrollable footer " + footerItem); + footerItem.setSelectable(false); + return mScrollableFooters.add(footerItem); + } + return false; + } + + public final void removeScrollableHeader(@NonNull T headerItem) { + if (mScrollableHeaders.remove(headerItem)) { + if (DEBUG) Log.d(TAG, "Remove scrollable header " + headerItem); + performRemove(headerItem, true); + } + } + + public final void removeScrollableFooter(@NonNull T footerItem) { + if (mScrollableFooters.remove(footerItem)) { + if (DEBUG) Log.d(TAG, "Remove scrollable footer " + footerItem); + performRemove(footerItem, true); + } + } + + public final void removeAllScrollableHeaders() { + if (DEBUG) Log.d(TAG, "Remove all scrollable headers"); + mScrollableHeaders.clear(); + } + + public final void removeAllScrollableFooters() { + if (DEBUG) Log.d(TAG, "Remove all scrollable footers"); + mScrollableFooters.clear(); + } + + 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) " + headerItem); + headerItem.setSelectable(false); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (!mScrollableHeaders.contains(headerItem)) { + performDelayedAdd(0, headerItem, scrollToPosition); + mScrollableHeaders.add(headerItem); + if (DEBUG) Log.v(TAG, "Added scrollable header " + headerItem); + } + } + }, delay); + } + + 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) " + footerItem); + footerItem.setSelectable(false); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (!mScrollableFooters.contains(footerItem)) { + performDelayedAdd(getItemCount(), footerItem, scrollToPosition); + mScrollableFooters.add(footerItem); + if (DEBUG) Log.v(TAG, "Added scrollable footer " + footerItem); + } + } + }, delay); + } + + public final void removeScrollableHeaderWithDelay(@NonNull final T headerItem, @IntRange(from = 0) long delay) { + if (DEBUG) + Log.d(TAG, "Enqueued removing scrollable header (" + delay + "ms) " + headerItem); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (mScrollableHeaders.remove(headerItem)) { + performDelayedRemove(headerItem, true); + if (DEBUG) Log.v(TAG, "Removed scrollable header " + headerItem); + } + } + }, delay); + } + + public final void removeScrollableFooterWithDelay(@NonNull final T footerItem, @IntRange(from = 0) long delay) { + if (DEBUG) + Log.d(TAG, "Enqueued removing scrollable footer (" + delay + "ms) " + footerItem); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (mScrollableFooters.remove(footerItem)) { + performDelayedRemove(footerItem, true); + if (DEBUG) Log.v(TAG, "Removed scrollable footer " + footerItem); + } + } + }, delay); + } + + private void restoreScrollableHeadersAndFooters(List items) { + if (items != null) { + for (T item : mScrollableHeaders) + if (items.size() > 0) items.add(0, item); + else items.add(item); + for (int i = mScrollableFooters.size() - 1; i >= 0; i--) + items.add(mScrollableFooters.get(i)); + } + } + /*--------------------------*/ /* HEADERS/SECTIONS METHODS */ /*--------------------------*/ @@ -917,8 +1098,8 @@ public boolean areHeadersSticky() { /** * Enables the sticky header functionality. - *

    Headers can be sticky only if they are shown. Command is otherwise ignored!

    - * NOTE: + *

    Headers can be sticky only if they are shown.

    + * Note: *
    - You must read {@link #getStickySectionHeadersHolder()}. *
    - Sticky headers are now clickable as any Views, but cannot be dragged nor swiped. *
    - Content and linkage are automatically updated. @@ -1083,9 +1264,9 @@ public void run() { */ private void showAllHeadersWithReset(boolean init) { multiRange = true; - int position = 0; + int position = Math.max(0, mScrollableHeaders.size() - 1); resetHiddenStatus();//Necessary after the filter and the update - while (position < mItems.size()) { + while (position < getItemCount() - mScrollableFooters.size()) { if (showHeaderOf(position, mItems.get(position), init)) position++;//It's the same element, skip it position++; @@ -1099,7 +1280,7 @@ 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) { @@ -1110,16 +1291,9 @@ private boolean showHeaderOf(int position, @NonNull T item, boolean init) { 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); - } + //Skip notifyItemInserted when init=true and insert the header properly! + return (init ? performCardinalInsert(position, Collections.singletonList((T) header)) : + addItem(position, (T) header)); } return false; } @@ -1140,9 +1314,9 @@ public void run() { 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); @@ -1286,7 +1460,7 @@ private void removeFromOrphanList(IHeader header) { //TODO: deprecation? 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 if (item instanceof IHeader) break; @@ -1496,18 +1670,17 @@ protected void onLoadMore(int position) { } else if (DEBUG) { Log.v(TAG, "onLoadMore loading=" + mLoading + ", position=" + position + ", itemCount=" + getItemCount() + ", threshold=" + mEndlessScrollThreshold - + ", inside the threshold? " + (position >= getItemCount() - mEndlessScrollThreshold)); + + ", inside the threshold? " + (position >= getItemCount() - mScrollableFooters.size() - mEndlessScrollThreshold)); } //Load more if not loading and inside the threshold - if (!mLoading && position >= getItemCount() - mEndlessScrollThreshold) { + if (!mLoading && position >= getItemCount() - mScrollableFooters.size() - mEndlessScrollThreshold) { mLoading = true; if (DEBUG) Log.d(TAG, "onLoadMore invoked!"); mRecyclerView.post(new Runnable() { @Override public void run() { if (getGlobalPositionOf(mProgressItem) < 0) { - mItems.add(mProgressItem); - notifyItemInserted(getItemCount()); + addItem(mProgressItem); } if (mEndlessScrollListener != null) { mEndlessScrollListener.onLoadMore(); @@ -1578,7 +1751,7 @@ private void deleteProgressItem() { */ private void noMoreLoad() { if (DEBUG) Log.i(TAG, "onLoadMore noMoreLoad!"); - notifyItemChanged(getItemCount() - 1, Payload.NO_MORE_LOAD); + notifyItemChanged(getItemCount() - mScrollableFooters.size() - 1, Payload.NO_MORE_LOAD); } /*--------------------*/ @@ -1752,34 +1925,35 @@ public int getExpandablePositionOf(@NonNull T child) { } /** - * Provides the full sub 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 an empty list if the child item has no parent + * @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 */ - @NonNull - public List getSiblingsOf(@NonNull T child) { - IExpandable expandable = getExpandableOf(child); - return expandable != null ? expandable.getSubItems() : new ArrayList<>(); + //TODO: Rename to getSubPositionOf + 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.

    + * Provides the full sub list where the child currently lays. * * @param child the child item - * @return the position in the parent or -1 if the 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 #getRelativePositionOf(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<>(); } /** @@ -1811,15 +1985,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 @@ -1953,7 +2128,9 @@ 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++) { + int startPosition = Math.max(0, mScrollableHeaders.size() - 1); + int endPosition = getItemCount() - mScrollableFooters.size() - 1; + for (int i = startPosition; i < endPosition; i++) { T item = getItem(i); if (isExpandable(item)) { IExpandable expandable = (IExpandable) item; @@ -2096,6 +2273,7 @@ public void updateItem(@IntRange(from = 0) int position, @NonNull T item, Log.e(TAG, "Cannot updateItem on position out of OutOfBounds!"); return; } + position += mScrollableHeaders.size(); mItems.set(position, item); if (DEBUG) Log.d(TAG, "updateItem notifyItemChanged on position " + position); notifyItemChanged(position, payload); @@ -2128,16 +2306,19 @@ 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); + performDelayedAdd(position, item, scrollToPosition); } }, delay); } + private void performDelayedAdd(int position, T item, boolean scrollToPosition) { + setAnimate(true); + if (addItem(position, item) && scrollToPosition && mRecyclerView != null) { + mRecyclerView.smoothScrollToPosition(Math.min(Math.max(0, position), getItemCount() - 1)); + } + setAnimate(false); + } + /** * Simply append the provided item to the end of the list. *

    Convenience method of {@link #addItem(int, IFlexible)} with @@ -2152,9 +2333,9 @@ public boolean addItem(@NonNull T 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). + * (no matters if the new position is out of bounds!). * - * @param position position of the item to add, if negative, items will be added to the end + * @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 #addItem(IFlexible) @@ -2169,16 +2350,16 @@ public boolean addItem(@IntRange(from = 0) int position, @NonNull T item) { 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 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.

    + * (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, if negative, items will be added to the end * @param items the set of items to add @@ -2200,12 +2381,8 @@ public boolean addItems(@IntRange(from = 0) int position, @NonNull List items } if (DEBUG) Log.d(TAG, "addItems on position=" + position + " itemCount=" + items.size()); - //Insert Items - if (position < mItems.size()) { - mItems.addAll(position, items); - } else { - mItems.addAll(items); - } + //Insert the item properly + performCardinalInsert(position, items); //Notify range addition notifyItemRangeInserted(position, items.size()); @@ -2218,10 +2395,25 @@ public boolean addItems(@IntRange(from = 0) int position, @NonNull List items } //Call listener to update EmptyView if (!recursive && mUpdateListener != null && !multiRange && initialCount == 0 && getItemCount() > 0) - mUpdateListener.onUpdateEmptyView(getItemCount()); + mUpdateListener.onUpdateEmptyView(getItemCount(false)); return true; } + private boolean performCardinalInsert(int position, List items) { + int itemCount = getItemCount(); + // Adjust position according to Headers/Footers size + int headersSize = mScrollableHeaders.size(); + int footersSize = mScrollableFooters.size(); + if (headersSize > 0 && position < itemCount) { + position += headersSize - 1; + } + if (footersSize > 0 && position >= itemCount) { + position -= footersSize; + } + // Insert Items + return (position < itemCount ? mItems.addAll(position, items) : mItems.addAll(items)); + } + /** * Convenience method of {@link #addSubItem(int, int, IFlexible, boolean, Object)}. *
    In this case parent item will never be expanded if it is collapsed. @@ -2474,8 +2666,8 @@ public int addItemToSection(@NonNull ISectionable item, @NonNull IHeader header, /*----------------------*/ /** - * Convenience method of {@link #removeRange(int, int, Object)} that removes all items, with - * {@code null} payload.

    + * This method clears everything: main items, Scrollable Headers and Footers and + * everything else is displayed. * * @see #clearAllBut(Integer...) * @see #removeRange(int, int) @@ -2485,11 +2677,14 @@ public int addItemToSection(@NonNull ISectionable item, @NonNull IHeader header, public void clear() { if (DEBUG) Log.d(TAG, "clearAll views"); removeRange(0, getItemCount(), null); + removeAllScrollableHeaders(); + removeAllScrollableFooters(); } /** - * Clears the Adapter list retaining all items of the type provided as parameter. - *

    This method is opposite of {@link #removeItemsOfType(Integer...)}.

    + * 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() @@ -2502,7 +2697,9 @@ public void clearAllBut(Integer... viewTypes) { if (viewTypeList.size() > 0) { if (DEBUG) Log.d(TAG, "clearAll retaining views " + viewTypeList); List positionsToRemove = new ArrayList<>(); - for (int i = 0; i < mItems.size(); i++) { + 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); } @@ -2545,18 +2742,27 @@ 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); + performDelayedRemove(item, permanent); } }, delay); } + private void performDelayedRemove(T item, boolean permanent) { + setAnimate(true); + performRemove(item, permanent); + setAnimate(false); + } + + 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) @@ -2666,7 +2872,9 @@ public int compare(Integer lhs, Integer rhs) { /** * Selectively removes all items of the type provided as parameter. - *

    This method is opposite of {@link #clearAllBut(Integer...)}.

    + *

    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() @@ -2679,7 +2887,9 @@ 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--) { + 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); } @@ -2748,7 +2958,8 @@ public void removeRange(@IntRange(from = 0) int positionStart, } //Handle header linkage - IHeader header = getHeaderOf(getItem(positionStart)); + 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 @@ -2759,7 +2970,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); @@ -2817,7 +3028,7 @@ public void removeRange(@IntRange(from = 0) int positionStart, //Update empty view if (mUpdateListener != null && !multiRange && initialCount > 0 && getItemCount() == 0) - mUpdateListener.onUpdateEmptyView(getItemCount()); + mUpdateListener.onUpdateEmptyView(getItemCount(false)); } /** @@ -2918,7 +3129,7 @@ public FlexibleAdapter setRestoreSelectionOnUndo(boolean restoreSelection) { /** * 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 @@ -3001,7 +3212,7 @@ public void restoreDeletedItems() { //Call listener to update EmptyView multiRange = false; if (mUpdateListener != null && initialCount == 0 && getItemCount() > 0) - mUpdateListener.onUpdateEmptyView(getItemCount()); + mUpdateListener.onUpdateEmptyView(getItemCount(false)); emptyBin(); } @@ -3234,22 +3445,23 @@ 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: *
      - *
    1. NEW! The Filter is always executed in background, asynchronously. + *
    2. The Filter is always executed in background, asynchronously. * The method {@link #onPostFilter()} is called after the filter task is completed.
    3. *
    4. This method calls {@link #filterObject(IFlexible, String)}.
    5. - *
    6. If search text is empty or null, the provided list is the current list.
    7. + *
    8. If searchText is empty or {@code null}, the provided list is the new list plus any + * Scrollable Headers and Footers if existent.
    9. *
    10. 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.
    11. - *
    12. NEW! Expandable items are picked up and displayed if at least a child is - * collected by the current filter.
    13. - *
    14. NEW! Items are animated thanks to {@link #animateTo(List, Payload)} 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.
    15. + *
    16. Expandable items are picked up and displayed if at least a child is collected by + * the current filter.
    17. + *
    18. 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)}.
    19. *
    * * @param unfilteredItems the list to filter @@ -3259,6 +3471,7 @@ public void filterItems(@NonNull List unfilteredItems, @IntRange(from = 0) lo * @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); @@ -3266,7 +3479,7 @@ 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! @@ -3317,6 +3530,7 @@ private synchronized void filterItemsAsync(@NonNull List unfilteredItems) { filteredItems.removeAll(getDeletedItems()); } resetFilterFlags(filteredItems); + restoreScrollableHeadersAndFooters(filteredItems); } //Reset flags @@ -3371,7 +3585,7 @@ private boolean filterExpandableObject(T item) { /** * 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 @@ -3449,7 +3663,7 @@ 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 @@ -3771,7 +3985,7 @@ public final boolean isLongPressDragEnabled() { /** * Enable / Disable the Drag on LongPress on the entire ViewHolder. - *

    NOTE: This will skip LongClick on the view in order to handle the LongPress, + *

    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}. @@ -4201,25 +4415,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 might remove duplicates during adjusting + //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 o1, Integer o2) { - return o2 - o1; + 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()); } /*----------------*/ @@ -4235,6 +4453,10 @@ public int compare(Integer o1, Integer o2) { public void onSaveInstanceState(Bundle outState) { if (outState != null) { //Save selection state + if (mScrollableHeaders.size() > 0) { + //We need to rollback the added item positions if headers were added + adjustSelected(0, -mScrollableHeaders.size()); + } super.onSaveInstanceState(outState); //Save selection coherence outState.putBoolean(EXTRA_CHILD, this.childSelected); @@ -4637,7 +4859,7 @@ private void postUpdate(boolean init) { onPostUpdate(); //Update empty view if (mUpdateListener != null) { - mUpdateListener.onUpdateEmptyView(getItemCount()); + mUpdateListener.onUpdateEmptyView(getItemCount(false)); } } @@ -4660,7 +4882,7 @@ private void postFilter() { onPostFilter(); //Call listener to update EmptyView, assuming the filter always made a change if (mUpdateListener != null) - mUpdateListener.onUpdateEmptyView(getItemCount()); + mUpdateListener.onUpdateEmptyView(getItemCount(hasSearchText())); } /** 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 6479fb2b..3de50100 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java @@ -299,10 +299,21 @@ public void toggleSelection(int position) { * @see #isSelectable(int) * @since 5.0.0-b7 */ - public boolean addSelection(int position) { + public final boolean addSelection(int position) { return isSelectable(position) && mSelectedPositions.add(position); } + /** + * This method is used only internally to force adjust selection. + * + * @param position Position of the item to add the selection status for. + * @return true if the set is modified, false otherwise + * @since 5.0.0-rc1 + */ + final boolean addAdjustedSelection(int position) { + return mSelectedPositions.add(position); + } + /** * Removes the selection status for the given position without notifying the change. * @@ -310,7 +321,7 @@ public boolean addSelection(int position) { * @return true if the set is modified, false otherwise * @since 5.0.0-b7 */ - public boolean removeSelection(int position) { + public final boolean removeSelection(int position) { return mSelectedPositions.remove(position); } From f23962d9319f5adc1ce65ddfdaa772481db151f2 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 23 Nov 2016 01:31:28 +0100 Subject: [PATCH 31/92] Resolves #236 - Scrollable Headers and Footers items Adapted demo App to use the new functionality --- .../flexibleadapter/ExampleAdapter.java | 27 +++--- .../samples/flexibleadapter/MainActivity.java | 10 +- .../flexibleadapter/OverallAdapter.java | 24 ++--- .../fragments/FragmentEndlessScrolling.java | 12 ++- .../FragmentExpandableMultiLevel.java | 4 +- .../fragments/FragmentExpandableSections.java | 4 +- .../fragments/FragmentHeadersSections.java | 9 +- .../fragments/FragmentInstagramHeaders.java | 2 +- .../fragments/FragmentOverall.java | 2 +- .../fragments/FragmentSelectionModes.java | 13 --- .../flexibleadapter/items/HeaderItem.java | 6 +- .../items/ScrollableFooterItem.java | 92 +++++++++++++++++++ ...outItem.java => ScrollableLayoutItem.java} | 23 ++--- .../{ULSItem.java => ScrollableULSItem.java} | 6 +- ...bel_item.xml => recycler_overall_item.xml} | 0 .../recycler_scrollable_footer_item.xml | 71 ++++++++++++++ .../recycler_scrollable_header_item.xml | 71 ++++++++++++++ ...ml => recycler_scrollable_layout_item.xml} | 0 ...m.xml => recycler_scrollable_uls_item.xml} | 0 .../recycler_scrollable_usecase_item.xml | 70 ++++++++++++++ .../src/main/res/values/strings.xml | 14 ++- 21 files changed, 381 insertions(+), 79 deletions(-) create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableFooterItem.java rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/{LayoutItem.java => ScrollableLayoutItem.java} (75%) rename flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/{ULSItem.java => ScrollableULSItem.java} (94%) rename flexible-adapter-app/src/main/res/layout/{recycler_label_item.xml => recycler_overall_item.xml} (100%) create mode 100644 flexible-adapter-app/src/main/res/layout/recycler_scrollable_footer_item.xml create mode 100644 flexible-adapter-app/src/main/res/layout/recycler_scrollable_header_item.xml rename flexible-adapter-app/src/main/res/layout/{recycler_layout_item.xml => recycler_scrollable_layout_item.xml} (100%) rename flexible-adapter-app/src/main/res/layout/{recycler_uls_item.xml => recycler_scrollable_uls_item.xml} (100%) create mode 100644 flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml 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 abe03452..18f2eea8 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 @@ -10,8 +10,9 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; -import eu.davidea.samples.flexibleadapter.items.LayoutItem; -import eu.davidea.samples.flexibleadapter.items.ULSItem; +import eu.davidea.samples.flexibleadapter.items.ScrollableFooterItem; +import eu.davidea.samples.flexibleadapter.items.ScrollableLayoutItem; +import eu.davidea.samples.flexibleadapter.items.ScrollableULSItem; import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration; /** @@ -61,7 +62,7 @@ public void updateDataSet(List items, boolean animate) { public void showLayoutInfo(boolean scrollToPosition) { if (!hasSearchText() && !isEmpty()) { //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)); @@ -75,9 +76,8 @@ public void showLayoutInfo(boolean scrollToPosition) { R.string.columns, String.valueOf(getSpanCount(mRecyclerView.getLayoutManager()))) ); - addItemWithDelay((getItem(0) instanceof ULSItem ? 1 : 0), item, 0L, - (!(getItem(0) instanceof ULSItem) && scrollToPosition)); - removeItemWithDelay(item, 4000L, true); + addScrollableHeader(item); + removeScrollableHeaderWithDelay(item, 4000L); } } @@ -88,18 +88,21 @@ public void showLayoutInfo(boolean scrollToPosition) { * 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)) { + if (!DatabaseConfiguration.userLearnedSelection && !hasSearchText() && !(getItem(0) instanceof ScrollableULSItem)) { //Define Example View - final ULSItem item = new ULSItem("ULS"); + 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, scrollToPosition); } } - @Override - protected void onPostFilter() { - addUserLearnedSelection(false); + public void addScrollableFooter() { + //Define Example View + 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); } /** 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 5302524f..270ddd45 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 @@ -85,9 +85,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 @@ -100,10 +100,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.

    */ @@ -667,7 +667,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } /* DIALOG LISTENER IMPLEMENTATION (For the example of onItemClick) */ - + @Override public void onTitleModified(int position, String newTitle) { AbstractFlexibleItem abstractItem = mAdapter.getItem(position); 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 d9a80c37..5a7c17f2 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 @@ -13,7 +13,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; import eu.davidea.flexibleadapter.items.IFlexible; -import eu.davidea.samples.flexibleadapter.items.LayoutItem; +import eu.davidea.samples.flexibleadapter.items.ScrollableLayoutItem; import eu.davidea.samples.flexibleadapter.items.OverallItem; import eu.davidea.samples.flexibleadapter.services.DatabaseService; import eu.davidea.utils.Utils; @@ -47,7 +47,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)); @@ -61,8 +61,8 @@ public void showLayoutInfo(boolean scrollToPosition) { R.string.columns, String.valueOf(getSpanCount(mRecyclerView.getLayoutManager()))) ); - addItemWithDelay(0, item, 500L, scrollToPosition); - removeItemWithDelay(item, 2000L, true); + addScrollableHeaderWithDelay(item, 500L, scrollToPosition); + removeScrollableHeaderWithDelay(item, 2000L); } } @@ -73,8 +73,8 @@ 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 ScrollableLayoutItem) return R.layout.recycler_scrollable_layout_item; + else return R.layout.recycler_overall_item; } /** @@ -85,8 +85,8 @@ 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_layout_item: + return new ScrollableLayoutItem.LayoutViewHolder( mInflater.inflate(viewType, parent, false), this); default: return new OverallItem.LabelViewHolder( @@ -103,9 +103,9 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType @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; + 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 @@ -118,7 +118,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; 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 bf0c003e..494bd6cc 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 @@ -109,9 +109,11 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mAdapter.setEndlessScrollListener(this, new ProgressItem()); //mAdapter.setEndlessScrollThreshold(1);//Default=1 - //Add sample HeaderView items on the top (not belongs to the library) + //Add sample Scrollable Header and Footer items (not belongs to the library) mAdapter.addUserLearnedSelection(savedInstanceState == null); mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.addScrollableFooter(); + } @Override @@ -171,7 +173,7 @@ public void run() { String message = (newItems.size() > 0 ? "Simulated: " + newItems.size() + " new items arrived :-)" : "Simulated: No more items to load :-(\nRefresh to retry."); - Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); } }, 2500); } @@ -214,8 +216,10 @@ public int getSpanSize(int position) { //NOTE: If you use simple integer 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_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 5adb483d..354c1dcd 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 @@ -112,8 +112,8 @@ public int getSpanSize(int position) { //NOTE: If you use simple integer 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 0bbb6a30..3223892d 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 @@ -119,8 +119,8 @@ public int getSpanSize(int position) { //NOTE: If you use simple integer 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 db5ea6a0..43ed214c 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 @@ -112,9 +112,10 @@ private void initializeRecyclerView(Bundle savedInstanceState) { swipeRefreshLayout.setEnabled(true); mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); - //Add sample HeaderView items on the top (not belongs to the library) + //Add sample Scrollable Header and Footer items (not belongs to the library) mAdapter.addUserLearnedSelection(savedInstanceState == null); mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.addScrollableFooter(); } @Override @@ -189,8 +190,10 @@ public int getSpanSize(int position) { //NOTE: If you use simple integer 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_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/FragmentInstagramHeaders.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentInstagramHeaders.java index 11636ccb..350ea4c6 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 @@ -118,7 +118,7 @@ public void run() { //Notify user String message = "Fetched " + newItems.size() + " new items"; - Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); } }, 2000); } 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 77e5de29..9590636b 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 @@ -120,7 +120,7 @@ public int getSpanSize(int position) { // NOTE: If you use simple integer 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_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 1cbbc4e5..2b659407 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 @@ -19,8 +19,6 @@ import eu.davidea.samples.flexibleadapter.ExampleAdapter; import eu.davidea.samples.flexibleadapter.MainActivity; import eu.davidea.samples.flexibleadapter.R; -import eu.davidea.samples.flexibleadapter.items.ULSItem; -import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration; import eu.davidea.samples.flexibleadapter.services.DatabaseService; import eu.davidea.utils.Utils; @@ -79,17 +77,6 @@ private void initializeRecyclerView(Bundle savedInstanceState) { //Get copy of the Database list List items = DatabaseService.getInstance().getDatabaseList(); - //TODO: find a solution for delayed items added after the restoring of the selected items - //The delayed items must ALREADY exist in the list before restoring the selection, otherwise - // the selection CANNOT be adjusted - if (savedInstanceState != null && !DatabaseConfiguration.userLearnedSelection) { - //Define Example View - final ULSItem item = new ULSItem("ULS"); - item.setTitle(getString(R.string.uls_title)); - item.setSubtitle(getString(R.string.uls_subtitle)); - items.add(0, item); - } - //Initialize Adapter and RecyclerView //ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()' mAdapter = new ExampleAdapter(items, getActivity()); diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java index ceb8c86a..e72d9b96 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java @@ -18,9 +18,9 @@ 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 { 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..8ff327ef --- /dev/null +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableFooterItem.java @@ -0,0 +1,92 @@ +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; +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.items.IFlexible; +import eu.davidea.samples.flexibleadapter.R; +import eu.davidea.viewholders.FlexibleViewHolder; + +/** + * This item is a Scrollable Footer. + */ +public class ScrollableFooterItem extends AbstractItem { + + private static final long serialVersionUID = -5041296095060813327L; + + public ScrollableFooterItem(String id) { + super(id); + } + + @Override + public boolean isSelectable() { + return false; + } + + @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, this); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, FooterViewHolder holder, int position, List payloads) { +// holder.mTitle.setSelected(true);//For marquee + holder.mTitle.setText(Html.fromHtml(getTitle())); + holder.mSubtitle.setText(Html.fromHtml(getSubtitle())); + } + + class FooterViewHolder extends FlexibleViewHolder { + + public TextView mTitle; + public TextView mSubtitle; + public ImageView mDismissIcon; + + public FooterViewHolder(View view, FlexibleAdapter adapter, final IFlexible item) { + 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 for Scrollable Headers and Footers + //mAdapter.setPermanentDelete(true); + mAdapter.removeScrollableFooter(item); + //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 String toString() { + return "FooterItem[" + super.toString() + "]"; + } + +} \ No newline at end of file diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/LayoutItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableLayoutItem.java similarity index 75% rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/LayoutItem.java rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableLayoutItem.java index 5d3fd875..de7a2db7 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/LayoutItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableLayoutItem.java @@ -17,18 +17,18 @@ import eu.davidea.viewholders.FlexibleViewHolder; /** - * Item dedicated to display the status of the Layout currently displayed (located always at - * position 0 in the Adapter). + * Item dedicated to display the status of the Layout currently displayed. + * This item is a Scrollable Header. * *

    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 AbstractItem { +public class ScrollableLayoutItem extends AbstractItem { private static final long serialVersionUID = -5041296095060813327L; - public LayoutItem(String id) { + public ScrollableLayoutItem(String id) { super(id); } @@ -44,16 +44,16 @@ public boolean isSelectable() { @Override public int getLayoutRes() { - return R.layout.recycler_layout_item; + return R.layout.recycler_scrollable_layout_item; } @Override - public ExampleViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) { - return new ExampleViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter); + public LayoutViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) { + return new LayoutViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter); } @Override - public void bindViewHolder(FlexibleAdapter adapter, ExampleViewHolder holder, int position, List payloads) { + public void bindViewHolder(FlexibleAdapter adapter, LayoutViewHolder holder, int position, List payloads) { holder.mTitle.setSelected(true);//For marquee holder.mTitle.setText(getTitle()); holder.mSubtitle.setText(getSubtitle()); @@ -65,15 +65,12 @@ public void bindViewHolder(FlexibleAdapter adapter, ExampleViewHolder holder, in } } - /** - * Used for UserLearnsSelection. - */ - public static class ExampleViewHolder extends FlexibleViewHolder { + public static class LayoutViewHolder extends FlexibleViewHolder { public TextView mTitle; public TextView mSubtitle; - public ExampleViewHolder(View view, FlexibleAdapter adapter) { + public LayoutViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter, true); 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/items/ULSItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java similarity index 94% rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ULSItem.java rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java index fbf9a0e8..ec36c813 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ULSItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java @@ -24,11 +24,11 @@ * {@link eu.davidea.flexibleadapter.items.AbstractFlexibleItem} to benefit of the already * implemented methods (getter and setters).

    */ -public class ULSItem extends AbstractItem { +public class ScrollableULSItem extends AbstractItem { private static final long serialVersionUID = -5041296095060813327L; - public ULSItem(String id) { + public ScrollableULSItem(String id) { super(id); } @@ -44,7 +44,7 @@ public boolean isSelectable() { @Override public int getLayoutRes() { - return R.layout.recycler_uls_item; + return R.layout.recycler_scrollable_uls_item; } @Override 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_footer_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_footer_item.xml new file mode 100644 index 00000000..5dc5cc15 --- /dev/null +++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_footer_item.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + \ 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..32139f76 --- /dev/null +++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_header_item.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + \ 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 100% 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 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 100% 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 diff --git a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml new file mode 100644 index 00000000..7c49b449 --- /dev/null +++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/flexible-adapter-app/src/main/res/values/strings.xml b/flexible-adapter-app/src/main/res/values/strings.xml index 2a0a4f60..8109a69f 100644 --- a/flexible-adapter-app/src/main/res/values/strings.xml +++ b/flexible-adapter-app/src/main/res/values/strings.xml @@ -35,9 +35,13 @@ Mode Single Undo - + Click on Image (or LongClick on Item) to select that Item]]> - This is an example of 2nd view type]]> + This is scrollable header.]]> + Scrollable Header Item + Scrollable Headers are always displayed at the top of all main items.]]> + Scrollable Footer Item + Scrollable Footers are always displayed at the bottom of all main items.]]> Title @@ -87,7 +91,7 @@ items and higher, due to calculate the correct position for each item to shift. Overall Selection modes - IDLE, SINGLE, MULTI + ActionModeHelper + UndoHelper + IDLE, SINGLE, MULTI + ActionModeHelper + UndoHelper + Scrollable Headers Async Filter Big list with Asynchronous filter and refresh + Synchronization Animations + Configuration @@ -96,7 +100,7 @@ items and higher, due to calculate the correct position for each item to shift. ItemAnimators coherent with ScrollingAnimation 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 Expandable Sections Sections with [sticky] headers that can expand/collapse + Draggable items + Filter + Scroll Animations @@ -105,7 +109,7 @@ 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.]]> From 31299c831ed8e85ef43e218c696e2c91f09fee62 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 23 Nov 2016 20:05:10 +0100 Subject: [PATCH 32/92] Adapted some items to the new concept of Scrollable Headers and Footers --- .../flexibleadapter/ExampleAdapter.java | 24 +++-- .../fragments/FragmentAnimators.java | 4 +- .../fragments/FragmentEndlessScrolling.java | 4 +- .../FragmentExpandableMultiLevel.java | 4 +- .../fragments/FragmentExpandableSections.java | 4 +- .../fragments/FragmentHeadersSections.java | 4 +- .../fragments/FragmentSelectionModes.java | 4 +- .../fragments/FragmentStaggeredLayout.java | 4 +- .../flexibleadapter/items/AbstractItem.java | 7 +- .../flexibleadapter/items/ProgressItem.java | 4 +- .../items/ScrollableFooterItem.java | 16 ++-- .../items/ScrollableLayoutItem.java | 4 +- .../items/ScrollableULSItem.java | 30 +++---- .../items/ScrollableUseCaseItem.java | 90 +++++++++++++++++++ 14 files changed, 145 insertions(+), 58 deletions(-) create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableUseCaseItem.java 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 18f2eea8..36d6e96c 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 @@ -5,6 +5,7 @@ import android.os.Message; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.StaggeredGridLayoutManager; +import android.view.ViewGroup; import java.util.List; @@ -55,11 +56,11 @@ public void updateDataSet(List items, boolean animate) { /* * 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) { + public void showLayoutInfo() { if (!hasSearchText() && !isEmpty()) { //Define Example View final ScrollableLayoutItem item = new ScrollableLayoutItem("LAY-L"); @@ -83,8 +84,7 @@ public void showLayoutInfo(boolean scrollToPosition) { /* * 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 shows 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) { @@ -97,6 +97,11 @@ public void addUserLearnedSelection(boolean scrollToPosition) { } } + /* + * FOOTER VIEW + * This method shows 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() { //Define Example View final ScrollableFooterItem item = new ScrollableFooterItem("SFI"); @@ -108,6 +113,8 @@ public void addScrollableFooter() { /** * 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() { @@ -160,10 +167,9 @@ public String onCreateBubbleText(int position) { * 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 and if you catch the reserved "what", - * keep in mind that this code should be executed before that task has been completed. - *

    + *

    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 { 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..b6e71281 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 @@ -116,7 +116,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); //Add sample HeaderView items on the top (not belongs to the library) - mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.showLayoutInfo(); } @Override @@ -130,7 +130,7 @@ public void performFabAction() { @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(true); + mAdapter.showLayoutInfo(); } @Override 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 494bd6cc..b6986bd9 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 @@ -111,7 +111,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { //Add sample Scrollable Header and Footer items (not belongs to the library) mAdapter.addUserLearnedSelection(savedInstanceState == null); - mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.showLayoutInfo(); mAdapter.addScrollableFooter(); } @@ -119,7 +119,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(true); + mAdapter.showLayoutInfo(); } /** 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 354c1dcd..16f43cc8 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 @@ -94,13 +94,13 @@ private void initializeRecyclerView(Bundle savedInstanceState) { //Add sample HeaderView items on the top (not belongs to the library) mAdapter.addUserLearnedSelection(savedInstanceState == null); - mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.showLayoutInfo(); } @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(true); + mAdapter.showLayoutInfo(); } @Override 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 3223892d..9d35164d 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 @@ -101,13 +101,13 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); //Add sample HeaderView items on the top (not belongs to the library) - mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.showLayoutInfo(); } @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(true); + mAdapter.showLayoutInfo(); } @Override 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 43ed214c..8f9eda31 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 @@ -114,7 +114,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { //Add sample Scrollable Header and Footer items (not belongs to the library) mAdapter.addUserLearnedSelection(savedInstanceState == null); - mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.showLayoutInfo(); mAdapter.addScrollableFooter(); } @@ -127,7 +127,7 @@ public void performFabAction() { @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(true); + mAdapter.showLayoutInfo(); } @Override 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 2b659407..674fcb0b 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 @@ -111,13 +111,13 @@ public void run() { //Add sample HeaderView items on the top (not belongs to the library) mAdapter.addUserLearnedSelection(savedInstanceState == null); - mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.showLayoutInfo(); } @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(true); + mAdapter.showLayoutInfo(); } @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 8afa3cde..db7751ba 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 @@ -96,7 +96,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); //Add sample HeaderView items on the top (not belongs to the library) - mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.showLayoutInfo(); } @Override @@ -107,7 +107,7 @@ public int getContextMenuResId() { @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(true); + mAdapter.showLayoutInfo(); } @Override diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java index 827c3b4e..9171bef4 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java @@ -1,7 +1,5 @@ package eu.davidea.samples.flexibleadapter.items; -import java.io.Serializable; - import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; import eu.davidea.viewholders.FlexibleViewHolder; @@ -12,10 +10,7 @@ * It is used as base item for all example models. */ public abstract class AbstractItem - extends AbstractFlexibleItem - implements Serializable { - - private static final long serialVersionUID = -6882745111884490060L; + extends AbstractFlexibleItem { protected String id; protected String title; 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 index ccec60fd..4afbcb8c 100644 --- 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 @@ -42,9 +42,9 @@ public void bindViewHolder(FlexibleAdapter adapter, ProgressViewHolder holder, i //nothing to bind } - public static class ProgressViewHolder extends FlexibleViewHolder { + static class ProgressViewHolder extends FlexibleViewHolder { - public ProgressBar progressBar; + ProgressBar progressBar; public ProgressViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); 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 index 8ff327ef..95234875 100644 --- 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 @@ -14,7 +14,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.helpers.AnimatorHelper; -import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.samples.flexibleadapter.R; import eu.davidea.viewholders.FlexibleViewHolder; @@ -23,8 +22,6 @@ */ public class ScrollableFooterItem extends AbstractItem { - private static final long serialVersionUID = -5041296095060813327L; - public ScrollableFooterItem(String id) { super(id); } @@ -41,7 +38,7 @@ public int getLayoutRes() { @Override public FooterViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) { - return new FooterViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter, this); + return new FooterViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter); } @Override @@ -53,11 +50,11 @@ public void bindViewHolder(FlexibleAdapter adapter, FooterViewHolder holder, int class FooterViewHolder extends FlexibleViewHolder { - public TextView mTitle; - public TextView mSubtitle; - public ImageView mDismissIcon; + TextView mTitle; + TextView mSubtitle; + ImageView mDismissIcon; - public FooterViewHolder(View view, FlexibleAdapter adapter, final IFlexible item) { + public FooterViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); mTitle = (TextView) view.findViewById(R.id.title); mSubtitle = (TextView) view.findViewById(R.id.subtitle); @@ -67,7 +64,8 @@ public FooterViewHolder(View view, FlexibleAdapter adapter, final IFlexible item public void onClick(View v) { //Don't need anymore to set permanent for Scrollable Headers and Footers //mAdapter.setPermanentDelete(true); - mAdapter.removeScrollableFooter(item); + //noinspection unchecked + mAdapter.removeScrollableFooter(ScrollableFooterItem.this); //mAdapter.setPermanentDelete(false); } }); 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 index de7a2db7..f184dbfa 100644 --- 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 @@ -17,7 +17,7 @@ import eu.davidea.viewholders.FlexibleViewHolder; /** - * Item dedicated to display the status of the Layout currently displayed. + * Item dedicated to display which Layout is currently displayed. * This item is a Scrollable Header. * *

    If you don't have many fields in common better to extend directly from @@ -26,8 +26,6 @@ */ public class ScrollableLayoutItem extends AbstractItem { - private static final long serialVersionUID = -5041296095060813327L; - public ScrollableLayoutItem(String id) { super(id); } diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java index ec36c813..6af362ef 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java @@ -24,9 +24,7 @@ * {@link eu.davidea.flexibleadapter.items.AbstractFlexibleItem} to benefit of the already * implemented methods (getter and setters).

    */ -public class ScrollableULSItem extends AbstractItem { - - private static final long serialVersionUID = -5041296095060813327L; +public class ScrollableULSItem extends AbstractItem { public ScrollableULSItem(String id) { super(id); @@ -48,12 +46,12 @@ public int getLayoutRes() { } @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 @@ -64,14 +62,14 @@ public void bindViewHolder(FlexibleAdapter adapter, ExampleViewHolder holder, in /** * 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) { + public ULSViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); mTitle = (TextView) view.findViewById(R.id.title); mSubtitle = (TextView) view.findViewById(R.id.subtitle); @@ -81,9 +79,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 for Scrollable Headers and Footers + //mAdapter.setPermanentDelete(true); + //noinspection unchecked + mAdapter.removeScrollableFooter(ScrollableULSItem.this); + //mAdapter.setPermanentDelete(false); } }); 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..af4aa1b2 --- /dev/null +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableUseCaseItem.java @@ -0,0 +1,90 @@ +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; +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.viewholders.FlexibleViewHolder; + +/** + * This item is a Scrollable Header. + */ +public class ScrollableUseCaseItem extends AbstractItem { + + public ScrollableUseCaseItem(String id) { + super(id); + } + + @Override + public boolean isSelectable() { + return false; + } + + @Override + public int getLayoutRes() { + return R.layout.recycler_scrollable_usecase_item; + } + + @Override + public HeaderViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) { + return new HeaderViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { +// holder.mTitle.setSelected(true);//For marquee + holder.mTitle.setText(Html.fromHtml(getTitle())); + holder.mSubtitle.setText(Html.fromHtml(getSubtitle())); + } + + class HeaderViewHolder extends FlexibleViewHolder { + + TextView mTitle; + TextView mSubtitle; + ImageView mDismissIcon; + + public HeaderViewHolder(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 for Scrollable Headers and Footers + //mAdapter.setPermanentDelete(true); + //noinspection unchecked + mAdapter.removeScrollableHeader(ScrollableUseCaseItem.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.slideInFromTopAnimator(animators, itemView, mAdapter.getRecyclerView()); + } + } + + @Override + public String toString() { + return "FooterItem[" + super.toString() + "]"; + } + +} \ No newline at end of file From b2481f9a874a8a569e0149d68798355a13ac69ac Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Thu, 24 Nov 2016 01:14:23 +0100 Subject: [PATCH 33/92] Working on Endless Scrolling improvements. --- .../fragments/FragmentEndlessScrolling.java | 19 +++-- .../flexibleadapter/items/HeaderItem.java | 6 +- .../items/InstagramHeaderItem.java | 6 +- .../flexibleadapter/items/InstagramItem.java | 10 +-- .../flexibleadapter/items/ProgressItem.java | 25 +++++- .../src/main/res/layout/progress_item.xml | 17 +++- .../src/main/res/values/strings.xml | 4 +- .../flexibleadapter/FlexibleAdapter.java | 82 +++++++++++++------ 8 files changed, 116 insertions(+), 53 deletions(-) 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 b6986bd9..80355580 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 @@ -107,13 +107,13 @@ private void initializeRecyclerView(Bundle savedInstanceState) { //EndlessScrollListener - OnLoadMore (v5.0.0) mAdapter.setEndlessScrollListener(this, new ProgressItem()); - //mAdapter.setEndlessScrollThreshold(1);//Default=1 +// .setEndlessTargetCount(10) +// .setEndlessScrollThreshold(1);//Default=1 //Add sample Scrollable Header and Footer items (not belongs to the library) mAdapter.addUserLearnedSelection(savedInstanceState == null); mAdapter.showLayoutInfo(); mAdapter.addScrollableFooter(); - } @Override @@ -144,17 +144,18 @@ public void run() { int count = new Random().nextInt(3); int totalItemsOfType = mAdapter.getItemCountOfTypes(R.layout.recycler_expandable_item); for (int i = 1; i <= count; i++) { - if (i % 2 != 0) { +// if (i % 2 != 0) { newItems.add(DatabaseService.newSimpleItem(totalItemsOfType + i, null)); - } else { - newItems.add(DatabaseService.newExpandableItem(totalItemsOfType + i, null)); - } +// } else { +// newItems.add(DatabaseService.newExpandableItem(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); + //- When list is null or empty, ProgressItem will be reset to null and endless + // scroll disabled + mAdapter.onLoadMoreComplete(newItems, 5000L); DatabaseService.getInstance().addAll(newItems); //Expand all Expandable items: Not Expandable items are automatically skipped/ignored! @@ -175,7 +176,7 @@ public void run() { "Simulated: No more items to load :-(\nRefresh to retry."); Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); } - }, 2500); + }, 2000); } @Override diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java index e72d9b96..8a069672 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java @@ -105,9 +105,9 @@ 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) { super(view, adapter, true);//True for sticky diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramHeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramHeaderItem.java index 6030cff9..85380739 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramHeaderItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramHeaderItem.java @@ -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/items/InstagramItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramItem.java index 1d87e01e..2214d6b3 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramItem.java @@ -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/items/ProgressItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ProgressItem.java index 4afbcb8c..3e4d421a 100644 --- 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 @@ -1,15 +1,20 @@ 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; @@ -39,16 +44,32 @@ public ProgressViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflat @Override public void bindViewHolder(FlexibleAdapter adapter, ProgressViewHolder holder, int position, List payloads) { - //nothing to bind + if (!payloads.contains(Payload.NO_MORE_LOAD) && adapter.isEndlessScrollEnabled()) { + holder.progressBar.setVisibility(View.VISIBLE); + holder.progressMessage.setVisibility(View.GONE); + } else if (!adapter.isEndlessScrollEnabled()) { + holder.progressBar.setVisibility(View.GONE); + holder.progressMessage.setVisibility(View.VISIBLE); + Context context = holder.itemView.getContext(); + holder.progressMessage.setText(context.getString(R.string.endless_finished)); + } else if (payloads.contains(Payload.NO_MORE_LOAD)) { + holder.progressBar.setVisibility(View.GONE); + holder.progressMessage.setVisibility(View.VISIBLE); + Context context = holder.itemView.getContext(); + holder.progressMessage.setText(context.getString(R.string.no_more_load)); + } } static class ProgressViewHolder extends FlexibleViewHolder { + @BindView(R.id.progress_bar) ProgressBar progressBar; + @BindView(R.id.progress_message) + TextView progressMessage; public ProgressViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); - progressBar = (ProgressBar) view.findViewById(R.id.progress_bar); + ButterKnife.bind(this, view); } @Override public void scrollAnimators(@NonNull List animators, int position, boolean isForward) { diff --git a/flexible-adapter-app/src/main/res/layout/progress_item.xml b/flexible-adapter-app/src/main/res/layout/progress_item.xml index 82e93beb..1a76141a 100644 --- a/flexible-adapter-app/src/main/res/layout/progress_item.xml +++ b/flexible-adapter-app/src/main/res/layout/progress_item.xml @@ -1,8 +1,9 @@ - + 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/values/strings.xml b/flexible-adapter-app/src/main/res/values/strings.xml index 8109a69f..84bfe80b 100644 --- a/flexible-adapter-app/src/main/res/values/strings.xml +++ b/flexible-adapter-app/src/main/res/values/strings.xml @@ -35,13 +35,15 @@ Mode Single Undo - + Click on Image (or LongClick on Item) to select that Item]]> This is scrollable header.]]> Scrollable Header Item Scrollable Headers are always displayed at the top of all main items.]]> 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. Title 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 a710b74c..af9d91a2 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -159,7 +159,7 @@ public class FlexibleAdapter private ItemTouchHelper mItemTouchHelper; /* EndlessScroll */ - private int mEndlessScrollThreshold = 1; + private int mEndlessScrollThreshold = 1, mEndlessTargetCount = 0; private boolean mLoading = false; private T mProgressItem; @@ -1292,8 +1292,12 @@ private boolean showHeaderOf(int position, @NonNull T item, boolean init) { if (DEBUG) Log.v(TAG, "Showing header at position " + position + " header=" + header); header.setHidden(false); //Skip notifyItemInserted when init=true and insert the header properly! - return (init ? performCardinalInsert(position, Collections.singletonList((T) header)) : - addItem(position, (T) header)); + if (init) { + performCardinalInsert(position, Collections.singletonList((T) header)); + } else { + addItem(position, (T) header); + } + return true; } return false; } @@ -1595,6 +1599,21 @@ else if (elevation > 0)//Leave unaltered the default elevation /* ENDLESS SCROLL METHODS */ /*------------------------*/ + /** + * Evaluates if the Adapter is in Endless Scroll mode. When no more load, the ProgressItem + * will be set to {@code null} and this method will return {@code false}. + * + * @return true if the progress item is set, false otherwise + */ + public boolean isEndlessScrollEnabled() { + return mProgressItem != null; + } + +// public FlexibleAdapter setEndlessTargetCount(@IntRange(from = 1) int endlessTargetCount) { +// mEndlessTargetCount = endlessTargetCount; +// return this; +// } + /** * Sets the ProgressItem to be displayed at the end of the list and activate the Loading More * functionality. @@ -1664,30 +1683,34 @@ public FlexibleAdapter setEndlessScrollThreshold(@IntRange(from = 1) int thresho * @param position the current binding position */ protected void onLoadMore(int position) { - //Skip everything when no loading more - if (mProgressItem == null || position == getGlobalPositionOf(mProgressItem)) { + // Skip everything when no loading more + int footersSize = mScrollableFooters.size(); + int limit = getItemCount() - mEndlessScrollThreshold - (hasSearchText() ? 0 : footersSize); + if (mProgressItem == null || position == getGlobalPositionOf(mProgressItem) + || position < limit || mLoading) { return; } else if (DEBUG) { Log.v(TAG, "onLoadMore loading=" + mLoading + ", position=" + position + ", itemCount=" + getItemCount() + ", threshold=" + mEndlessScrollThreshold - + ", inside the threshold? " + (position >= getItemCount() - mScrollableFooters.size() - mEndlessScrollThreshold)); + + ", inside the threshold? " + (position >= getItemCount() - mEndlessScrollThreshold - (hasSearchText() ? 0 : mScrollableFooters.size()))); } - //Load more if not loading and inside the threshold - if (!mLoading && position >= getItemCount() - mScrollableFooters.size() - mEndlessScrollThreshold) { - mLoading = true; - if (DEBUG) Log.d(TAG, "onLoadMore invoked!"); - mRecyclerView.post(new Runnable() { - @Override - public void run() { - if (getGlobalPositionOf(mProgressItem) < 0) { - addItem(mProgressItem); - } - if (mEndlessScrollListener != null) { - mEndlessScrollListener.onLoadMore(); - } + // Load more if not loading and inside the threshold + mLoading = true; + if (DEBUG) Log.d(TAG, "onLoadMore invoked!"); + // Insertion in post is 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() { + if (getGlobalPositionOf(mProgressItem) < 0) { + addItem(mProgressItem); } - }); - } + // When the listener is not set, loading more is called upon a user request + if (mEndlessScrollListener != null) { + mEndlessScrollListener.onLoadMore(); + } + } + }); } /** @@ -1716,7 +1739,6 @@ public void onLoadMoreComplete(@Nullable List newItems) { * @since 5.0.0-b8 */ public void onLoadMoreComplete(@Nullable List newItems, @IntRange(from = -1) long delay) { - //Handling the delay if (delay < 0) { //Disable the Automatic Endless functionality and keep the item mProgressItem = null; @@ -1729,6 +1751,8 @@ public void onLoadMoreComplete(@Nullable List newItems, @IntRange(from = -1) if (newItems != null && newItems.size() > 0) { if (DEBUG) Log.i(TAG, "onLoadMore performing adding " + newItems.size() + " new Items!"); + //Delete the progress + mHandler.sendEmptyMessage(LOAD_MORE_COMPLETE); addItems(getItemCount(), newItems); } else { noMoreLoad(); @@ -1751,7 +1775,7 @@ private void deleteProgressItem() { */ private void noMoreLoad() { if (DEBUG) Log.i(TAG, "onLoadMore noMoreLoad!"); - notifyItemChanged(getItemCount() - mScrollableFooters.size() - 1, Payload.NO_MORE_LOAD); + notifyItemChanged(getGlobalPositionOf(mProgressItem), Payload.NO_MORE_LOAD); } /*--------------------*/ @@ -2382,7 +2406,7 @@ public boolean addItems(@IntRange(from = 0) int position, @NonNull List items if (DEBUG) Log.d(TAG, "addItems on position=" + position + " itemCount=" + items.size()); //Insert the item properly - performCardinalInsert(position, items); + position = performCardinalInsert(position, items); //Notify range addition notifyItemRangeInserted(position, items.size()); @@ -2399,7 +2423,7 @@ public boolean addItems(@IntRange(from = 0) int position, @NonNull List items return true; } - private boolean performCardinalInsert(int position, List items) { + private int performCardinalInsert(int position, List items) { int itemCount = getItemCount(); // Adjust position according to Headers/Footers size int headersSize = mScrollableHeaders.size(); @@ -2411,7 +2435,13 @@ private boolean performCardinalInsert(int position, List items) { position -= footersSize; } // Insert Items - return (position < itemCount ? mItems.addAll(position, items) : mItems.addAll(items)); + if (position < itemCount) { + mItems.addAll(position, items); + } else { + mItems.addAll(items); + } + //return the new position + return position; } /** From 60d3834ef3518e7e39feab474704c6877320d62f Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Fri, 25 Nov 2016 09:06:24 +0100 Subject: [PATCH 34/92] Working on Endless Scrolling improvements with pageSize and targetCount #233 --- .../fragments/FragmentEndlessScrolling.java | 17 ++- .../fragments/FragmentInstagramHeaders.java | 18 ++- .../flexibleadapter/FlexibleAdapter.java | 135 +++++++++++++----- 3 files changed, 128 insertions(+), 42 deletions(-) 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 80355580..8912786d 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 @@ -122,11 +122,25 @@ public void showNewLayoutInfo(MenuItem item) { mAdapter.showLayoutInfo(); } + /** + * No more data to load. + * + * @since 5.0.0-rc1 + */ + @Override + public void noMoreLoad() { + + } + /** * Loads more data. + * + * @param lastPosition + * @param currentPage + * @since 5.0.0-rc1 */ @Override - public void onLoadMore() { + 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! @@ -155,6 +169,7 @@ public void run() { //- New items will be added to the end of the list //- When list is null or empty, ProgressItem will be reset to null and endless // scroll disabled + mAdapter.setEndlessTargetCount(10); mAdapter.onLoadMoreComplete(newItems, 5000L); DatabaseService.getInstance().addAll(newItems); 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 350ea4c6..2277ace4 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 @@ -93,11 +93,25 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); } + /** + * No more data to load. + * + * @since 5.0.0-rc1 + */ + @Override + public void noMoreLoad() { + + } + /** * Loads more data. + * + * @param lastPosition + * @param currentPage + * @since 5.0.0-rc1 */ @Override - public void onLoadMore() { + public void onLoadMore(int lastPosition, int currentPage) { Log.i(TAG, "onLoadMore invoked!"); //Simulating asynchronous call new Handler().postDelayed(new Runnable() { @@ -120,7 +134,7 @@ public void run() { String message = "Fetched " + newItems.size() + " new items"; Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); } - }, 2000); + }, 3000); } @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 af9d91a2..349c16b8 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -117,7 +117,7 @@ public class FlexibleAdapter private DiffUtilCallback diffUtilCallback; /* Handler for delayed actions */ - protected final int UPDATE = 0, FILTER = 1, CONFIRM_DELETE = 2, LOAD_MORE_COMPLETE = 8; + protected final int UPDATE = 0, FILTER = 1, CONFIRM_DELETE = 2, LOAD_MORE_COMPLETE = 8, LOAD_MORE_DISABLE = 9; protected Handler mHandler = new Handler(Looper.getMainLooper(), new HandlerCallback()); /* Deleted items and RestoreList (Undo) */ @@ -159,7 +159,8 @@ public class FlexibleAdapter private ItemTouchHelper mItemTouchHelper; /* EndlessScroll */ - private int mEndlessScrollThreshold = 1, mEndlessTargetCount = 0; + private int mEndlessScrollThreshold = 1, mEndlessTargetCount = 0, + mEndlessPageSize = 0, mCurrentPage = 1; private boolean mLoading = false; private T mProgressItem; @@ -1609,10 +1610,15 @@ public boolean isEndlessScrollEnabled() { return mProgressItem != null; } -// public FlexibleAdapter setEndlessTargetCount(@IntRange(from = 1) int endlessTargetCount) { -// mEndlessTargetCount = endlessTargetCount; -// return this; -// } + public FlexibleAdapter setEndlessTargetCount(@IntRange(from = 1) int endlessTargetCount) { + mEndlessTargetCount = endlessTargetCount; + return this; + } + + public FlexibleAdapter setEndlessPageSize(@IntRange(from = 1) int endlessPageSize) { + mEndlessPageSize = endlessPageSize; + return this; + } /** * Sets the ProgressItem to be displayed at the end of the list and activate the Loading More @@ -1628,11 +1634,15 @@ public boolean isEndlessScrollEnabled() { */ public FlexibleAdapter setEndlessProgressItem(@NonNull T progressItem) { if (progressItem != null) { - if (DEBUG) Log.i(TAG, "Set progressItem=" + progressItem.getClass().getSimpleName()); setEndlessScrollThreshold(mEndlessScrollThreshold); - progressItem.setEnabled(false); - mProgressItem = progressItem; + if (DEBUG) { + Log.i(TAG, "Set progressItem=" + progressItem.getClass().getSimpleName()); + Log.i(TAG, "Enabled EndlessScrolling"); + } + } else if (DEBUG) { + Log.i(TAG, "Disabled EndlessScrolling"); } + mProgressItem = progressItem; return this; } @@ -1683,11 +1693,15 @@ public FlexibleAdapter setEndlessScrollThreshold(@IntRange(from = 1) int thresho * @param position the current binding position */ protected void onLoadMore(int position) { - // Skip everything when no loading more + // Skip everything when loading more is unused OR currently loading OR all items are loaded + if (mProgressItem == null || mLoading || + (mEndlessTargetCount > 0 && getItemCount(false) >= mEndlessTargetCount)) + return; + + // Check next loading threshold int footersSize = mScrollableFooters.size(); int limit = getItemCount() - mEndlessScrollThreshold - (hasSearchText() ? 0 : footersSize); - if (mProgressItem == null || position == getGlobalPositionOf(mProgressItem) - || position < limit || mLoading) { + if (position == getGlobalPositionOf(mProgressItem) || position < limit) { return; } else if (DEBUG) { Log.v(TAG, "onLoadMore loading=" + mLoading + ", position=" + position @@ -1696,18 +1710,19 @@ protected void onLoadMore(int position) { } // Load more if not loading and inside the threshold mLoading = true; - if (DEBUG) Log.d(TAG, "onLoadMore invoked!"); // Insertion in post is 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() { - if (getGlobalPositionOf(mProgressItem) < 0) { + int lastPosition = getGlobalPositionOf(mProgressItem) - 1; + if (lastPosition < 0) { addItem(mProgressItem); } // When the listener is not set, loading more is called upon a user request if (mEndlessScrollListener != null) { - mEndlessScrollListener.onLoadMore(); + if (DEBUG) Log.d(TAG, "onLoadMore invoked!"); + mEndlessScrollListener.onLoadMore(lastPosition, mCurrentPage); } } }); @@ -1739,23 +1754,48 @@ public void onLoadMoreComplete(@Nullable List newItems) { * @since 5.0.0-b8 */ public void onLoadMoreComplete(@Nullable List newItems, @IntRange(from = -1) long delay) { - if (delay < 0) { - //Disable the Automatic Endless functionality and keep the item - mProgressItem = null; - } else { - //Delete the progress item with delay - mHandler.sendEmptyMessageDelayed(LOAD_MORE_COMPLETE, delay); - } - //Add the new items or reset the loading status + // Reset the loading status mLoading = false; - if (newItems != null && newItems.size() > 0) { + // Check if something has been loaded + int newItemSize = newItems == null ? 0 : newItems.size(); + int totalItemCount = newItemSize + getItemCount(false) + 1; //+1 for the progress item + int positionToNotify = getGlobalPositionOf(mProgressItem); + boolean alreadyDisabled = false; + + // Check if features are enabled and limits are reached + if (mEndlessPageSize > 0 && newItemSize < mEndlessPageSize || // Is feature enabled and Not enough items? + mEndlessTargetCount > 0 && totalItemCount >= mEndlessTargetCount) { // Is feature enabled and Max limit has been reached? + if (DEBUG) Log.v(TAG, "onLoadMore keep and disable the progressItem"); + // Keep the item and DISABLE the EndlessScroll feature + if (delay > 0) { + mHandler.sendEmptyMessageDelayed(LOAD_MORE_DISABLE, delay); + } else { + setEndlessProgressItem(null); + } + noMoreLoad(positionToNotify);// TODO: cannot notify with negative position?? + alreadyDisabled = true; + } else if (newItemSize > 0) { + delay = 0; // Reset the delay when loading more should continue + } + // DELETE the progress Item + if (!alreadyDisabled) { + if (delay > 0) { + mHandler.sendEmptyMessageDelayed(LOAD_MORE_COMPLETE, delay); + } else { + mHandler.sendEmptyMessage(LOAD_MORE_COMPLETE); + } + } + // Finally Add any new items + if (newItemSize > 0) { + // Calculate the current page + if (mEndlessTargetCount > 0) { + mCurrentPage = mEndlessTargetCount % totalItemCount; + } if (DEBUG) - Log.i(TAG, "onLoadMore performing adding " + newItems.size() + " new Items!"); - //Delete the progress - mHandler.sendEmptyMessage(LOAD_MORE_COMPLETE); + Log.v(TAG, "onLoadMore performing adding " + newItemSize + " new items on Page=" + mCurrentPage); addItems(getItemCount(), newItems); - } else { - noMoreLoad(); + } else if (!alreadyDisabled) { + noMoreLoad(positionToNotify); } } @@ -1763,19 +1803,24 @@ public void onLoadMoreComplete(@Nullable List newItems, @IntRange(from = -1) * Called when loading more should continue. */ private void deleteProgressItem() { - int progressPosition = getGlobalPositionOf(mProgressItem); - if (progressPosition >= 0) { + int positionToNotify = getGlobalPositionOf(mProgressItem); + if (positionToNotify >= 0) { + if (DEBUG) Log.v(TAG, "onLoadMore remove progressItem"); mItems.remove(mProgressItem); - notifyItemRemoved(progressPosition); + notifyItemRemoved(positionToNotify); } } /** * Called when no more items are loaded. */ - private void noMoreLoad() { - if (DEBUG) Log.i(TAG, "onLoadMore noMoreLoad!"); - notifyItemChanged(getGlobalPositionOf(mProgressItem), Payload.NO_MORE_LOAD); + private void noMoreLoad(int positionToNotify) { + if (DEBUG) Log.d(TAG, "onLoadMore noMoreLoad!"); + if (positionToNotify >= 0) + notifyItemChanged(positionToNotify, Payload.NO_MORE_LOAD); + if (mEndlessScrollListener != null) { + mEndlessScrollListener.noMoreLoad(); + } } /*--------------------*/ @@ -4677,13 +4722,20 @@ public interface OnStickyHeaderChangeListener { /** * @since 22/04/2016 */ - public interface EndlessScrollListener { + public interface EndlessScrollListener { /** * Loads more data. * - * @since 5.0.0-b6 + * @since 5.0.0-rc1 */ - void onLoadMore(); + void onLoadMore(int lastPosition, int currentPage); + + /** + * No more data to load. + * + * @since 5.0.0-rc1 + */ + void noMoreLoad(); } /** @@ -4932,6 +4984,7 @@ protected void onPostFilter() { *
    1 = async call for filterItems, optionally delayed. *
    2 = deleteConfirmed when Undo timeout is over. *
    8 = remove the progress item from the list, optionally delayed. + *
    9 = remove the progress item from the list and DISABLE the EndlessScroll. *

    Note: numbers 0-9 are reserved for the Adapter, use others.

    * * @since 5.0.0-rc1 @@ -4953,8 +5006,12 @@ public boolean handleMessage(Message message) { if (listener != null) listener.onDeleteConfirmed(); emptyBin(); return true; - case LOAD_MORE_COMPLETE: //onLoadMore remove progress item + case LOAD_MORE_COMPLETE: //remove progress item + deleteProgressItem(); + return true; + case LOAD_MORE_DISABLE: //remove progress item and DISABLE the EndlessScroll deleteProgressItem(); + setEndlessProgressItem(null); return true; } return false; From a9f95e38259bdb2bde269577b8146ecb325cedee Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 26 Nov 2016 01:30:12 +0100 Subject: [PATCH 35/92] Upgrade to API 25 --- build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 532dca91..92a4b752 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ ext { developerEmail = "dave.dna@gmail.com" //Library - libraryCode = 18 + libraryCode = 19 libraryVersion = "5.0.0-SNAPSHOT" libraryDate = " built on " + getDate() libraryDescription = "1 Adapter for SelectionMode, Undo, ViewHolders, Filter, FastScroller, Animations, Sticky Headers, Expandable, Draggable, Swipeable, EndlessScroll :-)" @@ -23,9 +23,9 @@ ext { //Support and Build tools version minSdk = 14 - targetSdk = 24 - buildTools = "24.0.3" - supportLib = "24.2.1" + targetSdk = 25 + buildTools = "25.0.1" + supportLib = "25.0.1" //Support Libraries dependencies supportDependencies = [ From 5a06d06a3471ae33a7b499b9e44398d253581531 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 26 Nov 2016 19:40:26 +0100 Subject: [PATCH 36/92] Added 2 static methods to Utils --- .../flexibleadapter/SelectableAdapter.java | 13 +++------- .../davidea/flexibleadapter/utils/Utils.java | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) 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 3de50100..cdf34da3 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java @@ -64,7 +64,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) @@ -181,21 +182,13 @@ public static int getSpanCount(RecyclerView.LayoutManager layoutManager) { * @since 2.0.0 */ public void setMode(@Mode int mode) { - if (DEBUG) Log.i(TAG, getModeName(mode) + " enabled"); + if (DEBUG) Log.i(TAG, Utils.getModeName(mode) + " enabled"); if (mMode == MODE_SINGLE && mode == MODE_IDLE) clearSelection(); this.mMode = mode; mLastItemInActionMode = (mode == MODE_IDLE); } - private String getModeName(int mode) { - switch (mode) { - case 1: return "MODE_SINGLE"; - case 2: return "MODE_MULTI"; - default: return "MODE_IDLE"; - } - } - /** * The current selection mode of the Adapter. * diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java index 433efa4b..de1f4df0 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java @@ -15,6 +15,7 @@ */ package eu.davidea.flexibleadapter.utils; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; @@ -36,6 +37,7 @@ import java.util.Locale; import eu.davidea.flexibleadapter.R; +import eu.davidea.flexibleadapter.SelectableAdapter; /** * @author Davide Steduto @@ -195,4 +197,26 @@ else if (context instanceof ContextWrapper) return null; } + /** + * @return the string representation of the provided {@link SelectableAdapter.Mode} + */ + @SuppressLint("SwitchIntDef") + public static String getModeName(@SelectableAdapter.Mode int mode) { + switch (mode) { + case SelectableAdapter.MODE_SINGLE: + return "MODE_SINGLE"; + case SelectableAdapter.MODE_MULTI: + return "MODE_MULTI"; + default: + return "MODE_IDLE"; + } + } + + /** + * @return the SimpleClassName of the provided object + */ + public static String getClassName(@NonNull Object o) { + return o.getClass().getSimpleName(); + } + } \ No newline at end of file From 6a9a2066a7e9298cd6f8b4f560a2080668c82ce9 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 28 Nov 2016 01:13:55 +0100 Subject: [PATCH 37/92] Resolves #236 - Reviewed how Scrollable Headers and Footers items are handled: - Fixed refreshing the list. - Fixed screen rotation with selected items. - Fixed delayed adding and removing without impacts for the current selection. - Adding, updating, removing, moving and swapping main items with specific positions, must always be done via previous call getGlobalPositionOf() as usual. - Updating the empty view gives the size of the main items, excluding headers/footers (this must be evaluated with the others developers). - Fixed showing / hiding headers positions. - Modified the demoApp to use the new items Resolves #233 - New EndlessScrollListener behaviors for onLoadMore() and onLoadMoreComplete(). Loading more now handles: - New items less than pageSize previously set. - Total item count reached the target previously set. - None of the above is set, the Adapter will always attempt to load more. - Endless scroll / loading more: progressItem becomes a footer item but it will be always displayed between the main items and the others footers. - Prepared the code to loading more on the Top. - Modified the demoApp to show how to use the new progressItem --- .../fragments/FragmentAnimators.java | 24 +- .../fragments/FragmentEndlessScrolling.java | 126 +++-- .../FragmentExpandableMultiLevel.java | 34 +- .../fragments/FragmentExpandableSections.java | 42 +- .../fragments/FragmentHeadersSections.java | 34 +- .../fragments/FragmentHolderSections.java | 2 +- .../fragments/FragmentInstagramHeaders.java | 65 ++- .../fragments/FragmentOverall.java | 10 +- .../fragments/FragmentSelectionModes.java | 22 +- .../fragments/FragmentStaggeredLayout.java | 2 +- .../flexibleadapter/items/ProgressItem.java | 15 +- .../items/ScrollableFooterItem.java | 37 +- .../items/ScrollableULSItem.java | 4 +- .../services/DatabaseService.java | 4 +- .../src/main/res/layout/progress_item.xml | 2 +- .../res/layout/recycler_animator_sub_item.xml | 2 +- .../src/main/res/values/strings.xml | 4 +- .../flexibleadapter/AnimatorAdapter.java | 9 +- .../flexibleadapter/FlexibleAdapter.java | 484 +++++++++--------- .../flexibleadapter/SelectableAdapter.java | 5 +- 20 files changed, 513 insertions(+), 414 deletions(-) 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 b6e71281..d964a307 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 @@ -67,29 +67,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,23 +99,23 @@ 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) + // Add 1 Scrollable Header mAdapter.showLayoutInfo(); } @@ -238,7 +238,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/FragmentEndlessScrolling.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentEndlessScrolling.java index 8912786d..9a83f45a 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,6 +25,7 @@ import eu.davidea.samples.flexibleadapter.ExampleAdapter; import eu.davidea.samples.flexibleadapter.MainActivity; import eu.davidea.samples.flexibleadapter.R; +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; @@ -61,56 +62,57 @@ 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(1);//N. of items + // Create New Database and Initialize RecyclerView + 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) - //.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!!! + // 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 drag using handle view - .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 drag using handle view + .setSwipeEnabled(true) //Enable swipe items + .setDisplayHeadersAtStartUp(true); //Show Headers at startUp! 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()); -// .setEndlessTargetCount(10) -// .setEndlessScrollThreshold(1);//Default=1 + // EndlessScrollListener - OnLoadMore (v5.0.0) + mAdapter.setEndlessScrollListener(this, new ProgressItem()) + .setEndlessPageSize(3) + .setEndlessTargetCount(15); +// .setEndlessScrollThreshold(1); //Default=1 - //Add sample Scrollable Header and Footer items (not belongs to the library) + // Add 2 Scrollable Headers and 1 Footer items mAdapter.addUserLearnedSelection(savedInstanceState == null); mAdapter.showLayoutInfo(); mAdapter.addScrollableFooter(); @@ -122,74 +124,86 @@ public void showNewLayoutInfo(MenuItem item) { mAdapter.showLayoutInfo(); } + /** * 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() { - + 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.getItemCount(false)); } /** * 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 - * @param currentPage - * @since 5.0.0-rc1 + * @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(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 + // 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; } - //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(3); + // Simulating success/failure with Random + int count = new Random().nextInt(10); int totalItemsOfType = mAdapter.getItemCountOfTypes(R.layout.recycler_expandable_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 reset to null and endless - // scroll disabled - mAdapter.setEndlessTargetCount(10); - mAdapter.onLoadMoreComplete(newItems, 5000L); + // 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 + mAdapter.onLoadMoreComplete(newItems); DatabaseService.getInstance().addAll(newItems); + // It's better to read the page 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! + // Expand all Expandable items: Not Expandable items are automatically skipped/ignored! for (AbstractFlexibleItem item : newItems) { - //Simple expansion is performed: + // Simple expansion is performed: // - Automatic scroll is performed //mAdapter.expand(item); - //Initialization is performed: + // Initialization is performed: // - Expanded status is ignored(WARNING: possible subItem duplication) // - Automatic scroll is skipped mAdapter.expand(item, true); } - //Notify user - String message = (newItems.size() > 0 ? - "Simulated: " + newItems.size() + " new items arrived :-)" : - "Simulated: No more items to load :-(\nRefresh to retry."); - Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); + // Notify user + if (getActivity() != null && newItems.size() > 0) { + Toast.makeText(getActivity(), + "Simulated: " + newItems.size() + " new items arrived :-)", + Toast.LENGTH_SHORT).show(); + } } }, 2000); } @@ -229,8 +243,8 @@ 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_scrollable_header_item: case R.layout.recycler_scrollable_footer_item: 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 16f43cc8..5b79a1f9 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,50 @@ 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!) + .setMinCollapsibleLevel(1) //Auto-collapse only items with level >= 1 (avoid to collapse also sections!) .setAutoScrollOnExpand(true) .setRemoveOrphanHeaders(false); 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!!! + // 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! 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(); } @@ -109,8 +109,8 @@ 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_scrollable_layout_item: case R.layout.recycler_scrollable_uls_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 9d35164d..b9bd9892 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,29 +51,29 @@ 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 + .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) .setAnimationOnScrolling(DatabaseConfiguration.animateOnScrolling) .setAnimationOnReverseScrolling(true); @@ -81,26 +81,26 @@ private void initializeRecyclerView(Bundle savedInstanceState) { 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!!! + // 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(); } @@ -116,8 +116,8 @@ 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_scrollable_layout_item: case R.layout.recycler_scrollable_uls_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 8f9eda31..fe40a57d 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 @@ -62,57 +62,57 @@ 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) + // 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 + .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!!! + // 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! + // 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 Scrollable Header and Footer items (not belongs to the library) + // Add 2 Scrollable Headers and 1 Footer mAdapter.addUserLearnedSelection(savedInstanceState == null); mAdapter.showLayoutInfo(); mAdapter.addScrollableFooter(); @@ -165,7 +165,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() { @@ -187,8 +187,8 @@ 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_scrollable_header_item: case R.layout.recycler_scrollable_footer_item: 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 index 1a625a48..92b5d630 100644 --- 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 @@ -74,7 +74,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { 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.addUserLearnedSelection(savedInstanceState == null); } 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 2277ace4..1a164caa 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,6 +14,7 @@ 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; @@ -52,19 +53,19 @@ 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) { + 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); @@ -76,17 +77,17 @@ private void initializeRecyclerView(Bundle savedInstanceState) { 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! + .enableStickyHeaders() //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); @@ -95,25 +96,36 @@ private void initializeRecyclerView(Bundle savedInstanceState) { /** * 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() { - + 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 - * @param currentPage - * @since 5.0.0-rc1 + * @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(int lastPosition, int currentPage) { Log.i(TAG, "onLoadMore invoked!"); - //Simulating asynchronous call + // Simulating asynchronous call new Handler().postDelayed(new Runnable() { @SuppressWarnings("unchecked") @Override @@ -126,13 +138,20 @@ public void run() { 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(getActivity(), 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(); + } } }, 3000); } 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 9590636b..802013af 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 @@ -74,17 +74,17 @@ private void initializeRecyclerView(Bundle savedInstanceState) { // 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(); } } @@ -94,7 +94,7 @@ public void run() { 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); } @@ -117,7 +117,7 @@ protected GridLayoutManager createNewGridLayoutManager() { gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { - // NOTE: If you use simple integer to identify the ViewType, + // 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_scrollable_layout_item: 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 674fcb0b..feedcccb 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 @@ -61,14 +61,14 @@ public FragmentSelectionModes() { @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(); } @@ -77,21 +77,21 @@ private void initializeRecyclerView(Bundle savedInstanceState) { //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()' + // 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 + 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() { @@ -101,7 +101,7 @@ public void run() { } }, 1500L); - //Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!! + // 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()); @@ -109,7 +109,7 @@ public void run() { 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(); } 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 db7751ba..483e6007 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 @@ -95,7 +95,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { 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(); } 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 index 3e4d421a..5e5d3c4a 100644 --- 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 @@ -47,16 +47,15 @@ public void bindViewHolder(FlexibleAdapter adapter, ProgressViewHolder holder, i if (!payloads.contains(Payload.NO_MORE_LOAD) && adapter.isEndlessScrollEnabled()) { holder.progressBar.setVisibility(View.VISIBLE); holder.progressMessage.setVisibility(View.GONE); - } else if (!adapter.isEndlessScrollEnabled()) { + } else { holder.progressBar.setVisibility(View.GONE); holder.progressMessage.setVisibility(View.VISIBLE); Context context = holder.itemView.getContext(); - holder.progressMessage.setText(context.getString(R.string.endless_finished)); - } else if (payloads.contains(Payload.NO_MORE_LOAD)) { - holder.progressBar.setVisibility(View.GONE); - holder.progressMessage.setVisibility(View.VISIBLE); - Context context = holder.itemView.getContext(); - holder.progressMessage.setText(context.getString(R.string.no_more_load)); + if (!adapter.isEndlessScrollEnabled()) { + holder.progressMessage.setText(context.getString(R.string.endless_disabled)); + } else if (payloads.contains(Payload.NO_MORE_LOAD)) { + holder.progressMessage.setText(context.getString(R.string.no_more_load_retry)); + } } } @@ -67,7 +66,7 @@ static class ProgressViewHolder extends FlexibleViewHolder { @BindView(R.id.progress_message) TextView progressMessage; - public ProgressViewHolder(View view, FlexibleAdapter adapter) { + ProgressViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); ButterKnife.bind(this, view); } 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 index 95234875..61fb823b 100644 --- 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 @@ -2,11 +2,14 @@ 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.text.Html; 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; @@ -15,6 +18,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.helpers.AnimatorHelper; import eu.davidea.samples.flexibleadapter.R; +import eu.davidea.viewholders.AnimatedViewHolder; import eu.davidea.viewholders.FlexibleViewHolder; /** @@ -48,13 +52,13 @@ public void bindViewHolder(FlexibleAdapter adapter, FooterViewHolder holder, int holder.mSubtitle.setText(Html.fromHtml(getSubtitle())); } - class FooterViewHolder extends FlexibleViewHolder { + class FooterViewHolder extends FlexibleViewHolder implements AnimatedViewHolder { TextView mTitle; TextView mSubtitle; ImageView mDismissIcon; - public FooterViewHolder(View view, FlexibleAdapter adapter) { + FooterViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); mTitle = (TextView) view.findViewById(R.id.title); mSubtitle = (TextView) view.findViewById(R.id.subtitle); @@ -80,6 +84,35 @@ public void onClick(View v) { 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 diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java index 6af362ef..73ffd6f9 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java @@ -69,7 +69,7 @@ class ULSViewHolder extends FlexibleViewHolder { TextView mSubtitle; ImageView mDismissIcon; - public ULSViewHolder(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); @@ -82,7 +82,7 @@ public void onClick(View v) { //Don't need anymore to set permanent for Scrollable Headers and Footers //mAdapter.setPermanentDelete(true); //noinspection unchecked - mAdapter.removeScrollableFooter(ScrollableULSItem.this); + mAdapter.removeScrollableHeader(ScrollableULSItem.this); //mAdapter.setPermanentDelete(false); } }); 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 dbf31cf4..3d3a575e 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 @@ -55,7 +55,7 @@ public class DatabaseService { private Map headers; - DatabaseService() { + private DatabaseService() { } public static DatabaseService getInstance() { @@ -81,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(); diff --git a/flexible-adapter-app/src/main/res/layout/progress_item.xml b/flexible-adapter-app/src/main/res/layout/progress_item.xml index 1a76141a..d78f589c 100644 --- a/flexible-adapter-app/src/main/res/layout/progress_item.xml +++ b/flexible-adapter-app/src/main/res/layout/progress_item.xml @@ -17,7 +17,7 @@ android:id="@+id/progress_message" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/no_more_load" + android:text="@string/no_more_load_retry" android:layout_gravity="center" android:visibility="gone" tools:visibility="visible"/> 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"> Scrollable Headers are always displayed at the top of all main items.]]> 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. + No more items to load. Refresh to retry. + No more items to load. Title 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 2c56d221..6044664d 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -40,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. @@ -193,7 +195,7 @@ public AnimatorAdapter setAnimationDuration(@IntRange(from = 1) long duration) { * @return this AnimatorAdapter, so the call can be chained */ public AnimatorAdapter setAnimationInterpolator(@NonNull Interpolator interpolator) { - if (DEBUG) Log.i(TAG, "Set animationInterpolator=" + interpolator.getClass().getSimpleName()); + if (DEBUG) Log.i(TAG, "Set animationInterpolator=" + getClassName(interpolator)); mInterpolator = interpolator; return this; } @@ -342,6 +344,8 @@ private void cancelExistingAnimation(final int hashCode) { * @param position the current item position */ protected 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(); @@ -362,7 +366,7 @@ protected void animateView(final RecyclerView.ViewHolder holder, final int posit // } if (holder instanceof FlexibleViewHolder && shouldAnimate && !isFastScroll && !mAnimatorNotifierObserver.isPositionNotified() && - (isReverseEnabled || position > mLastAnimatedPosition || (position == 0 && mRecyclerView.getChildCount() == 0)) ) { + (isReverseEnabled || position > mLastAnimatedPosition || (position == 0 && mRecyclerView.getChildCount() == 0))) { //Cancel animation is necessary when fling int hashCode = holder.itemView.hashCode(); @@ -752,7 +756,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 349c16b8..d4430dd9 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -60,6 +60,8 @@ import eu.davidea.viewholders.ExpandableViewHolder; import eu.davidea.viewholders.FlexibleViewHolder; +import static eu.davidea.flexibleadapter.utils.Utils.getClassName; + /** * 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 @@ -100,9 +102,9 @@ 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"; - private static final String EXTRA_DELAYED_HEADERS = TAG + "_delayedHeaders"; /* The main container for ALL items */ private List mItems, mTempItems; @@ -117,7 +119,7 @@ public class FlexibleAdapter private DiffUtilCallback diffUtilCallback; /* Handler for delayed actions */ - protected final int UPDATE = 0, FILTER = 1, CONFIRM_DELETE = 2, LOAD_MORE_COMPLETE = 8, LOAD_MORE_DISABLE = 9; + protected final int UPDATE = 0, FILTER = 1, CONFIRM_DELETE = 2, LOAD_MORE_COMPLETE = 8; protected Handler mHandler = new Handler(Looper.getMainLooper(), new HandlerCallback()); /* Deleted items and RestoreList (Undo) */ @@ -159,9 +161,8 @@ public class FlexibleAdapter private ItemTouchHelper mItemTouchHelper; /* EndlessScroll */ - private int mEndlessScrollThreshold = 1, mEndlessTargetCount = 0, - mEndlessPageSize = 0, mCurrentPage = 1; - private boolean mLoading = false; + private int mEndlessScrollThreshold = 1, mEndlessTargetCount = 0, mEndlessPageSize = 0; + private boolean endlessLoading = false, endlessScrollEnabled = false; private T mProgressItem; /* Listeners */ @@ -257,7 +258,7 @@ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners, bool @CallSuper public FlexibleAdapter initializeListeners(@Nullable Object listener) { if (DEBUG && listener != null) { - Log.i(TAG, "Initialize Class " + listener.getClass().getSimpleName() + " as:"); + Log.i(TAG, "Initialize Class " + getClassName(listener) + " as:"); } if (listener instanceof OnItemClickListener) { if (DEBUG) Log.i(TAG, "- OnItemClickListener"); @@ -496,12 +497,13 @@ public void updateDataSet(List items) { */ @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 { - mItems = (items == null ? new ArrayList() : items); + mItems = items; postUpdate(true); } } @@ -567,6 +569,7 @@ public final int getItemCount() { * @see #getItemCountOfTypesUntil(int, Integer...) * @since 5.0.0-rc1 */ + //TODO: Rename to getMainItemCount? public final int getItemCount(boolean withHeadersFooters) { return withHeadersFooters ? getItemCount() : getItemCount() - mScrollableHeaders.size() - mScrollableFooters.size(); @@ -640,9 +643,9 @@ public final int getGlobalPositionOf(@NonNull IFlexible item) { * 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 cannot be overridden. - *
    - This method is NOT suitable to call when removing items, if so, you should retrieve - * the global position with {@link #getGlobalPositionOf(IFlexible)}.

    + *
    - 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 @@ -714,6 +717,7 @@ public int calculatePositionFor(@NonNull Object item, @Nullable Comparator compa /* SCROLLABLE HEADERS/FOOTERS METHODS */ /*------------------------------------*/ + //TODO: add javaDocs public final List getScrollableHeaders() { return Collections.unmodifiableList(mScrollableHeaders); } @@ -723,75 +727,92 @@ public final List getScrollableFooters() { } public final boolean addScrollableHeader(@NonNull T headerItem) { - if (DEBUG) Log.d(TAG, "Add scrollable header " + headerItem); - if (!mScrollableHeaders.contains(headerItem) && addItem(0, headerItem)) { + if (DEBUG) Log.d(TAG, "Add scrollable header " + getClassName(headerItem)); + if (!mScrollableHeaders.contains(headerItem)) { headerItem.setSelectable(false); - return mScrollableHeaders.add(headerItem); + int progressFix = 0;//(headerItem == mProgressItem) ? mScrollableHeaders.size() : 0; + mScrollableHeaders.add(headerItem); + performInsert(progressFix, Collections.singletonList(headerItem), true); + return true; + } else { + Log.w(TAG, "Scrollable header " + getClassName(headerItem) + " already exists"); + return false; } - return false; } public final boolean addScrollableFooter(@NonNull T footerItem) { - if (!mScrollableFooters.contains(footerItem) && addItem(getItemCount(), footerItem)) { - if (DEBUG) Log.d(TAG, "Add scrollable footer " + footerItem); + if (!mScrollableFooters.contains(footerItem)) { + if (DEBUG) Log.d(TAG, "Add scrollable footer " + getClassName(footerItem)); footerItem.setSelectable(false); - return mScrollableFooters.add(footerItem); + 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; } - return false; } public final void removeScrollableHeader(@NonNull T headerItem) { if (mScrollableHeaders.remove(headerItem)) { - if (DEBUG) Log.d(TAG, "Remove scrollable header " + headerItem); + if (DEBUG) Log.d(TAG, "Remove scrollable header " + getClassName(headerItem)); performRemove(headerItem, true); } } public final void removeScrollableFooter(@NonNull T footerItem) { if (mScrollableFooters.remove(footerItem)) { - if (DEBUG) Log.d(TAG, "Remove scrollable footer " + footerItem); + if (DEBUG) Log.d(TAG, "Remove scrollable footer " + getClassName(footerItem)); performRemove(footerItem, true); } } public final void removeAllScrollableHeaders() { - if (DEBUG) Log.d(TAG, "Remove all scrollable headers"); - mScrollableHeaders.clear(); + if (mScrollableHeaders.size() > 0) { + if (DEBUG) Log.d(TAG, "Remove all scrollable headers"); + mItems.removeAll(mScrollableHeaders); + notifyItemRangeRemoved(0, mScrollableHeaders.size()); + mScrollableHeaders.clear(); + } } public final void removeAllScrollableFooters() { - if (DEBUG) Log.d(TAG, "Remove all scrollable footers"); - mScrollableFooters.clear(); + 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(); + } } 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) " + headerItem); - headerItem.setSelectable(false); + if (DEBUG) + Log.d(TAG, "Enqueued adding scrollable header (" + delay + "ms) " + getClassName(headerItem)); mHandler.postDelayed(new Runnable() { @Override public void run() { - if (!mScrollableHeaders.contains(headerItem)) { - performDelayedAdd(0, headerItem, scrollToPosition); - mScrollableHeaders.add(headerItem); - if (DEBUG) Log.v(TAG, "Added scrollable header " + headerItem); - } + if (addScrollableHeader(headerItem) && scrollToPosition) + performScroll(getGlobalPositionOf(headerItem)); } }, delay); } 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) " + footerItem); - footerItem.setSelectable(false); + if (DEBUG) + Log.d(TAG, "Enqueued adding scrollable footer (" + delay + "ms) " + getClassName(footerItem)); mHandler.postDelayed(new Runnable() { @Override public void run() { - if (!mScrollableFooters.contains(footerItem)) { - performDelayedAdd(getItemCount(), footerItem, scrollToPosition); - mScrollableFooters.add(footerItem); - if (DEBUG) Log.v(TAG, "Added scrollable footer " + footerItem); - } + if (addScrollableFooter(footerItem) && scrollToPosition) + performScroll(getGlobalPositionOf(footerItem)); } }, delay); } @@ -802,36 +823,28 @@ public final void removeScrollableHeaderWithDelay(@NonNull final T headerItem, @ mHandler.postDelayed(new Runnable() { @Override public void run() { - if (mScrollableHeaders.remove(headerItem)) { - performDelayedRemove(headerItem, true); - if (DEBUG) Log.v(TAG, "Removed scrollable header " + headerItem); - } + removeScrollableHeader(headerItem); } }, delay); } public final void removeScrollableFooterWithDelay(@NonNull final T footerItem, @IntRange(from = 0) long delay) { if (DEBUG) - Log.d(TAG, "Enqueued removing scrollable footer (" + delay + "ms) " + footerItem); + Log.d(TAG, "Enqueued removing scrollable footer (" + delay + "ms) " + getClassName(footerItem)); mHandler.postDelayed(new Runnable() { @Override public void run() { - if (mScrollableFooters.remove(footerItem)) { - performDelayedRemove(footerItem, true); - if (DEBUG) Log.v(TAG, "Removed scrollable footer " + footerItem); - } + removeScrollableFooter(footerItem); } }, delay); } private void restoreScrollableHeadersAndFooters(List items) { - if (items != null) { - for (T item : mScrollableHeaders) - if (items.size() > 0) items.add(0, item); - else items.add(item); - for (int i = mScrollableFooters.size() - 1; i >= 0; i--) - items.add(mScrollableFooters.get(i)); - } + for (T item : mScrollableHeaders) + if (items.size() > 0) items.add(0, item); + else items.add(item); + for (T item : mScrollableFooters) + items.add(item); } /*--------------------------*/ @@ -1161,6 +1174,7 @@ public void run() { * @return ViewGroup layout that will hold the sticky header ItemViews * @since 5.0.0-b6 */ + //TODO: Review the comment for the new stickyHeaders helper //TODO: Rename the method to getStickyHeaderContainer public ViewGroup getStickySectionHeadersHolder() { if (mStickyContainer == null) { @@ -1180,7 +1194,7 @@ public ViewGroup getStickySectionHeadersHolder() { */ public FlexibleAdapter setStickyHeaderContainer(@NonNull ViewGroup stickyContainer) { if (DEBUG && stickyContainer != null) - Log.i(TAG, "Set stickyHeaderContainer=" + stickyContainer.getClass().getSimpleName()); + Log.i(TAG, "Set stickyHeaderContainer=" + getClassName(stickyContainer)); this.mStickyContainer = stickyContainer; return this; } @@ -1241,7 +1255,7 @@ private void showAllHeaders(boolean init) { public void run() { //#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 setDisplayHeadersAtStartUp() was already called!"); return; } showAllHeadersWithReset(false); @@ -1265,10 +1279,17 @@ public void run() { */ private void showAllHeadersWithReset(boolean init) { multiRange = true; - int position = Math.max(0, mScrollableHeaders.size() - 1); - resetHiddenStatus();//Necessary after the filter and the update + int position = 0; + IHeader sameHeader = null; + //resetHiddenStatus();//Necessary after the filter and the update while (position < getItemCount() - mScrollableFooters.size()) { - if (showHeaderOf(position, mItems.get(position), init)) + T item = mItems.get(position); + 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++; } @@ -1293,11 +1314,8 @@ private boolean showHeaderOf(int position, @NonNull T item, boolean init) { if (DEBUG) Log.v(TAG, "Showing header at position " + position + " header=" + header); header.setHidden(false); //Skip notifyItemInserted when init=true and insert the header properly! - if (init) { - performCardinalInsert(position, Collections.singletonList((T) header)); - } else { - addItem(position, (T) header); - } + if (init) performInsert(position, Collections.singletonList((T) header), false); + else addItem(position, (T) header); return true; } return false; @@ -1329,7 +1347,7 @@ public void run() { } headersShown = false; // Clear the header currently sticky - if (mStickyHeaderHelper != null) { + if (areHeadersSticky()) { mStickyHeaderHelper.clearHeaderWithAnimation(); } //setStickyHeaders(false); @@ -1371,7 +1389,9 @@ private boolean hideHeader(int position, IHeader header) { * the process is very fast. * * @since 5.0.0-b6 + * @deprecated Not used anymore. */ + @Deprecated private void resetHiddenStatus() { for (T item : mItems) { IHeader header = getHeaderOf(item); @@ -1563,10 +1583,8 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { @Override public void onBindViewHolder(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()); + Log.v(TAG, "onViewBound Holder=" + getClassName(holder) + " position=" + position + + " itemId=" + holder.getItemId() + " layoutPosition=" + holder.getLayoutPosition()); } if (!autoMap) { //If everything has been set properly, this should never happen ;-) @@ -1601,18 +1619,23 @@ else if (elevation > 0)//Leave unaltered the default elevation /*------------------------*/ /** - * Evaluates if the Adapter is in Endless Scroll mode. When no more load, the ProgressItem - * will be set to {@code null} and this method will return {@code false}. + * 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 set again the progressItem. * * @return true if the progress item is set, false otherwise + * @see #setEndlessProgressItem(IFlexible) */ public boolean isEndlessScrollEnabled() { - return mProgressItem != null; + return endlessScrollEnabled; } - public FlexibleAdapter setEndlessTargetCount(@IntRange(from = 1) int endlessTargetCount) { - mEndlessTargetCount = endlessTargetCount; - return this; + //TODO: add javaDocs + public int getEndlessCurrentPage() { + return Math.max(1, mEndlessPageSize > 0 ? getItemCount(false) / mEndlessPageSize : 0); + } + + public int getEndlessPageSize() { + return mEndlessPageSize; } public FlexibleAdapter setEndlessPageSize(@IntRange(from = 1) int endlessPageSize) { @@ -1620,48 +1643,59 @@ public FlexibleAdapter setEndlessPageSize(@IntRange(from = 1) int endlessPageSiz return this; } + public int getEndlessTargetCount() { + return mEndlessTargetCount; + } + + public FlexibleAdapter setEndlessTargetCount(@IntRange(from = 1) int endlessTargetCount) { + mEndlessTargetCount = endlessTargetCount; + return this; + } + /** - * Sets the ProgressItem to be displayed at the end of the list and activate the Loading More - * functionality. + * 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); + mProgressItem = progressItem; if (DEBUG) { - Log.i(TAG, "Set progressItem=" + progressItem.getClass().getSimpleName()); + Log.i(TAG, "Set progressItem=" + getClassName(progressItem)); Log.i(TAG, "Enabled EndlessScrolling"); } } else if (DEBUG) { Log.i(TAG, "Disabled EndlessScrolling"); } - mProgressItem = progressItem; 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 */ //TODO: Deprecation? use setProgressItem + setEndlessScrollListener public FlexibleAdapter setEndlessScrollListener(@Nullable EndlessScrollListener endlessScrollListener, @NonNull T progressItem) { - if (DEBUG) - Log.i(TAG, "Set endlessScrollListener=" + endlessScrollListener.getClass().getSimpleName()); + if (DEBUG) Log.i(TAG, "Set endlessScrollListener=" + getClassName(endlessScrollListener)); mEndlessScrollListener = endlessScrollListener; return setEndlessProgressItem(progressItem); } @@ -1691,38 +1725,38 @@ public FlexibleAdapter setEndlessScrollThreshold(@IntRange(from = 1) int thresho * 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) { - // Skip everything when loading more is unused OR currently loading OR all items are loaded - if (mProgressItem == null || mLoading || - (mEndlessTargetCount > 0 && getItemCount(false) >= mEndlessTargetCount)) + // Skip everything when loading more is unused OR currently loading + if (!isEndlessScrollEnabled() || endlessLoading) return; // Check next loading threshold - int footersSize = mScrollableFooters.size(); - int limit = getItemCount() - mEndlessScrollThreshold - (hasSearchText() ? 0 : footersSize); - if (position == getGlobalPositionOf(mProgressItem) || position < limit) { + int threshold = getItemCount() - mEndlessScrollThreshold - (hasSearchText() ? 0 : mScrollableFooters.size()); + if (position == getGlobalPositionOf(mProgressItem) || position < threshold) { return; } else if (DEBUG) { - Log.v(TAG, "onLoadMore loading=" + mLoading + ", position=" + position + 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 - mLoading = true; - // Insertion in post is suggested by Android because: java.lang.IllegalStateException: + 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() { - int lastPosition = getGlobalPositionOf(mProgressItem) - 1; - if (lastPosition < 0) { - addItem(mProgressItem); - } + // 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(lastPosition, mCurrentPage); + mEndlessScrollListener.onLoadMore(getItemCount(false), getEndlessCurrentPage()); } } }); @@ -1742,84 +1776,76 @@ 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.

    * 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 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) { - // Reset the loading status - mLoading = false; - // Check if something has been loaded - int newItemSize = newItems == null ? 0 : newItems.size(); - int totalItemCount = newItemSize + getItemCount(false) + 1; //+1 for the progress item - int positionToNotify = getGlobalPositionOf(mProgressItem); - boolean alreadyDisabled = false; - - // Check if features are enabled and limits are reached - if (mEndlessPageSize > 0 && newItemSize < mEndlessPageSize || // Is feature enabled and Not enough items? + // 1. Calculate new items count + int newItemsSize = newItems == null ? 0 : newItems.size(); + int totalItemCount = newItemsSize + getItemCount(false); + // 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? - if (DEBUG) Log.v(TAG, "onLoadMore keep and disable the progressItem"); - // Keep the item and DISABLE the EndlessScroll feature - if (delay > 0) { - mHandler.sendEmptyMessageDelayed(LOAD_MORE_DISABLE, delay); - } else { - setEndlessProgressItem(null); - } - noMoreLoad(positionToNotify);// TODO: cannot notify with negative position?? - alreadyDisabled = true; - } else if (newItemSize > 0) { - delay = 0; // Reset the delay when loading more should continue - } - // DELETE the progress Item - if (!alreadyDisabled) { - if (delay > 0) { - mHandler.sendEmptyMessageDelayed(LOAD_MORE_COMPLETE, delay); - } else { - mHandler.sendEmptyMessage(LOAD_MORE_COMPLETE); - } + // Disable the EndlessScroll feature + setEndlessProgressItem(null); } - // Finally Add any new items - if (newItemSize > 0) { - // Calculate the current page - if (mEndlessTargetCount > 0) { - mCurrentPage = mEndlessTargetCount % totalItemCount; - } + // 4. Remove the progressItem if needed + if (delay > 0 && (newItemsSize == 0 || !isEndlessScrollEnabled())) { if (DEBUG) - Log.v(TAG, "onLoadMore performing adding " + newItemSize + " new items on Page=" + mCurrentPage); - addItems(getItemCount(), newItems); - } else if (!alreadyDisabled) { - noMoreLoad(positionToNotify); + Log.v(TAG, "onLoadMore enqueued removing progressItem (" + delay + "ms)"); + mHandler.sendEmptyMessageDelayed(LOAD_MORE_COMPLETE, delay); + } else if (isEndlessScrollEnabled()) { + hideProgressItem(); + } + // 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() { + private void hideProgressItem() { int positionToNotify = getGlobalPositionOf(mProgressItem); if (positionToNotify >= 0) { if (DEBUG) Log.v(TAG, "onLoadMore remove progressItem"); - mItems.remove(mProgressItem); - notifyItemRemoved(positionToNotify); + removeScrollableFooter(mProgressItem); } } /** * Called when no more items are loaded. */ - private void noMoreLoad(int positionToNotify) { - if (DEBUG) Log.d(TAG, "onLoadMore noMoreLoad!"); + 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(); + mEndlessScrollListener.noMoreLoad(newItemsSize); } } @@ -2127,7 +2153,7 @@ private int expand(int position, boolean expandAll, boolean init) { " expanded " + expandable.isExpanded()); return 0; } - if (DEBUG && !init) { + if (DEBUG && !init && !expandAll) { Log.v(TAG, "Request to Expand on position=" + position + " expanded=" + expandable.isExpanded() + " anyParentSelected=" + parentSelected); @@ -2150,6 +2176,8 @@ private int expand(int position, boolean expandAll, boolean init) { //Save expanded state expandable.setExpanded(true); + //TODO: Check if the expandable is a Scrollable Header/Footer, add all the subItems to that list + //Automatically smooth scroll the current expandable item to show as much // children as possible if (!init && scrollOnExpand && !expandAll) { @@ -2198,12 +2226,12 @@ public int expandAll(int level) { int expanded = 0; //More efficient if we expand from First expandable position int startPosition = Math.max(0, mScrollableHeaders.size() - 1); - int endPosition = getItemCount() - mScrollableFooters.size() - 1; - for (int i = startPosition; i < endPosition; i++) { + 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++; } } @@ -2248,6 +2276,8 @@ public int collapse(@IntRange(from = 0) int position) { //Save expanded state expandable.setExpanded(false); + //TODO: Check if the expandable is a Scrollable Header/Footer, remove all the subItems from that list + //Collapse! notifyItemRangeRemoved(position + 1, subItemsCount); //Hide also the headers of the subItems @@ -2299,6 +2329,7 @@ public int collapseAll() { * @since 5.0.0-b6 */ public int collapseAll(int level) { + //Use hashSet to increase performance while removing subItems mHashItems = new LinkedHashSet<>(mItems); int collapsed = recursiveCollapse(0, mItems, level); mItems = new ArrayList<>(mHashItems); @@ -2338,11 +2369,11 @@ 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 >= getItemCount()) { + int itemCount = getItemCount(); + if (position < 0 || position >= itemCount) { Log.e(TAG, "Cannot updateItem on position out of OutOfBounds!"); return; } - position += mScrollableHeaders.size(); mItems.set(position, item); if (DEBUG) Log.d(TAG, "updateItem notifyItemChanged on position " + position); notifyItemChanged(position, payload); @@ -2375,19 +2406,11 @@ public void addItemWithDelay(@IntRange(from = 0) final int position, @NonNull fi mHandler.postDelayed(new Runnable() { @Override public void run() { - performDelayedAdd(position, item, scrollToPosition); + if (addItem(position, item) && scrollToPosition) performScroll(position); } }, delay); } - private void performDelayedAdd(int position, T item, boolean scrollToPosition) { - setAnimate(true); - if (addItem(position, item) && scrollToPosition && mRecyclerView != null) { - mRecyclerView.smoothScrollToPosition(Math.min(Math.max(0, position), getItemCount() - 1)); - } - setAnimate(false); - } - /** * Simply append the provided item to the end of the list. *

    Convenience method of {@link #addItem(int, IFlexible)} with @@ -2443,17 +2466,13 @@ public boolean addItems(@IntRange(from = 0) int position, @NonNull List items Log.e(TAG, "addItems No items to add!"); return false; } - int initialCount = getItemCount(); + int initialCount = getItemCount(false);//Count only main items! if (position < 0) { Log.w(TAG, "addItems Position is negative! adding items to the end"); position = initialCount; } - if (DEBUG) Log.d(TAG, "addItems on position=" + position + " itemCount=" + items.size()); - //Insert the item properly - position = performCardinalInsert(position, items); - //Notify range addition - notifyItemRangeInserted(position, items.size()); + performInsert(position, items, true); //Show the headers of these items if all headers are already visible if (headersShown && !recursive) { @@ -2468,25 +2487,20 @@ public boolean addItems(@IntRange(from = 0) int position, @NonNull List items return true; } - private int performCardinalInsert(int position, List items) { + private void performInsert(int position, List items, boolean notify) { int itemCount = getItemCount(); - // Adjust position according to Headers/Footers size - int headersSize = mScrollableHeaders.size(); - int footersSize = mScrollableFooters.size(); - if (headersSize > 0 && position < itemCount) { - position += headersSize - 1; - } - if (footersSize > 0 && position >= itemCount) { - position -= footersSize; - } - // Insert Items 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()); } - //return the new position - return position; } /** @@ -2751,9 +2765,9 @@ public int addItemToSection(@NonNull ISectionable item, @NonNull IHeader header, */ public void clear() { if (DEBUG) Log.d(TAG, "clearAll views"); - removeRange(0, getItemCount(), null); removeAllScrollableHeaders(); removeAllScrollableFooters(); + removeRange(0, getItemCount(), null); } /** @@ -2817,17 +2831,11 @@ public void removeItemWithDelay(@NonNull final T item, @IntRange(from = 0) long mHandler.postDelayed(new Runnable() { @Override public void run() { - performDelayedRemove(item, permanent); + performRemove(item, permanent); } }, delay); } - private void performDelayedRemove(T item, boolean permanent) { - setAnimate(true); - performRemove(item, permanent); - setAnimate(false); - } - private void performRemove(T item, boolean permanent) { boolean tempPermanent = permanentDelete; if (permanent) permanentDelete = true; @@ -3022,6 +3030,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(); @@ -3075,15 +3084,11 @@ public void removeRange(@IntRange(from = 0) int 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 @@ -3676,11 +3681,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); } /** @@ -3821,7 +3822,6 @@ private synchronized void animateDiff(@Nullable List newItems, Payload payloa *
    5.0.0-b8 Synchronization animation limit */ private synchronized void animateTo(@Nullable List newItems, Payload payloadChange) { - if (newItems == null) newItems = new ArrayList<>(); mNotifications = new ArrayList<>(); if (newItems.size() <= mAnimateToLimit) { if (DEBUG) @@ -4175,8 +4175,7 @@ public void moveItem(int fromPosition, int toPosition, @Nullable Object payload) if (expanded) collapse(toPosition); //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 @@ -4355,7 +4354,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)); } } @@ -4458,6 +4457,12 @@ private boolean hasSubItemsSelected(int startPosition, List subItems) { 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 new Handler(Looper.getMainLooper(), new Handler.Callback() { @@ -4478,9 +4483,9 @@ public boolean handleMessage(Message message) { 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; } @@ -4529,7 +4534,7 @@ public void onSaveInstanceState(Bundle outState) { if (outState != null) { //Save selection state if (mScrollableHeaders.size() > 0) { - //We need to rollback the added item positions if headers were added + //We need to rollback the added item positions if headers were added lately adjustSelected(0, -mScrollableHeaders.size()); } super.onSaveInstanceState(outState); @@ -4541,6 +4546,8 @@ public void onSaveInstanceState(Bundle outState) { outState.putString(EXTRA_SEARCH, this.mSearchText); //Save headers shown status outState.putBoolean(EXTRA_HEADERS, this.headersShown); + //TODO: enableStickyHeaders + //outState.putBoolean(EXTRA_STICKY, areHeadersSticky()); } } @@ -4560,8 +4567,16 @@ public void onRestoreInstanceState(Bundle savedInstanceState) { showAllHeadersWithReset(true); } this.headersShown = headersShown; + //TODO: enableStickyHeaders +// if (savedInstanceState.getBoolean(EXTRA_STICKY) && !areHeadersSticky()) { +// setStickyHeaders(true); +// } //Restore selection state super.onRestoreInstanceState(savedInstanceState); + 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); @@ -4580,9 +4595,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#getItemCount(boolean)} * @since 5.0.0-b1 */ void onUpdateEmptyView(int size); @@ -4595,8 +4613,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 @@ -4644,8 +4662,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 @@ -4669,7 +4687,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); @@ -4682,7 +4700,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); @@ -4723,19 +4741,32 @@ public interface OnStickyHeaderChangeListener { * @since 22/04/2016 */ public interface EndlessScrollListener { + /** - * Loads more data. + * 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 onLoadMore(int lastPosition, int currentPage); + void noMoreLoad(int newItemsSize); /** - * No more data to load. + * 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. * - * @since 5.0.0-rc1 + * @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 noMoreLoad(); + void onLoadMore(int lastPosition, int currentPage); } /** @@ -4983,8 +5014,7 @@ protected void onPostFilter() { * 0 = async call for updateDataSet. *
    1 = async call for filterItems, optionally delayed. *
    2 = deleteConfirmed when Undo timeout is over. - *
    8 = remove the progress item from the list, optionally delayed. - *
    9 = remove the progress item from the list and DISABLE the EndlessScroll. + *
    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 @@ -5006,12 +5036,8 @@ public boolean handleMessage(Message message) { if (listener != null) listener.onDeleteConfirmed(); emptyBin(); return true; - case LOAD_MORE_COMPLETE: //remove progress item - deleteProgressItem(); - return true; - case LOAD_MORE_DISABLE: //remove progress item and DISABLE the EndlessScroll - deleteProgressItem(); - setEndlessProgressItem(null); + case LOAD_MORE_COMPLETE: //hide progress item + hideProgressItem(); return true; } return false; 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 cdf34da3..46d1b523 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java @@ -443,6 +443,8 @@ public List getSelectedPositions() { */ public void onSaveInstanceState(Bundle outState) { outState.putIntegerArrayList(TAG, new ArrayList<>(mSelectedPositions)); + if (DEBUG && getSelectedItemCount() > 0) + Log.d(TAG, "Saving selection " + mSelectedPositions); } /** @@ -453,7 +455,8 @@ public void onSaveInstanceState(Bundle outState) { */ public void onRestoreInstanceState(Bundle savedInstanceState) { mSelectedPositions.addAll(savedInstanceState.getIntegerArrayList(TAG)); - Log.d(TAG, "Restore selection " + mSelectedPositions); + if (DEBUG && getSelectedItemCount() > 0) + Log.d(TAG, "Restore selection " + mSelectedPositions); } /*---------------*/ From b2d8c2f2a3baae4bbb33cd6dd7777c59bd52eb4e Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 28 Nov 2016 17:10:46 +0100 Subject: [PATCH 38/92] Resolves #236 - Added comments to the methods --- .../flexibleadapter/FlexibleAdapter.java | 128 +++++++++++++++++- 1 file changed, 126 insertions(+), 2 deletions(-) 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 d4430dd9..43eb2589 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -717,15 +717,46 @@ public int calculatePositionFor(@NonNull Object item, @Nullable Comparator compa /* SCROLLABLE HEADERS/FOOTERS METHODS */ /*------------------------------------*/ - //TODO: add javaDocs + /** + * @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); } + /** + * Adds a Scrollable Header. + *

    Scrollable Headers have the following characteristic: + *

      + *
    • lay always before any main item.
    • + *
    • cannot be selectable.
    • + *
    • 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 + */ public final boolean addScrollableHeader(@NonNull T headerItem) { if (DEBUG) Log.d(TAG, "Add scrollable header " + getClassName(headerItem)); if (!mScrollableHeaders.contains(headerItem)) { @@ -740,6 +771,28 @@ public final boolean addScrollableHeader(@NonNull T headerItem) { } } + /** + * Adds a Scrollable Footer. + *

    Scrollable Footers have the following characteristic: + *

      + *
    • lay always after any main item.
    • + *
    • cannot be selectable.
    • + *
    • cannot be inserted twice, but many can be inserted.
    • + *
    • 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)); @@ -759,6 +812,14 @@ public final boolean addScrollableFooter(@NonNull T footerItem) { } } + /** + * 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)); @@ -766,6 +827,14 @@ public final void removeScrollableHeader(@NonNull T headerItem) { } } + /** + * 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)); @@ -773,6 +842,13 @@ public final void removeScrollableFooter(@NonNull T footerItem) { } } + /** + * 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"); @@ -782,6 +858,13 @@ public final void removeAllScrollableHeaders() { } } + /** + * 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"); @@ -791,6 +874,16 @@ public final void removeAllScrollableFooters() { } } + /** + * 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) @@ -804,6 +897,16 @@ public void run() { }, 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) @@ -817,6 +920,15 @@ public void run() { }, 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) " + headerItem); @@ -828,6 +940,15 @@ public void run() { }, 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)); @@ -839,6 +960,10 @@ public void run() { }, 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); @@ -1692,7 +1817,6 @@ public FlexibleAdapter setEndlessProgressItem(@Nullable T progressItem) { * @see #setEndlessProgressItem(IFlexible) * @since 5.0.0-b6 */ - //TODO: Deprecation? use setProgressItem + setEndlessScrollListener public FlexibleAdapter setEndlessScrollListener(@Nullable EndlessScrollListener endlessScrollListener, @NonNull T progressItem) { if (DEBUG) Log.i(TAG, "Set endlessScrollListener=" + getClassName(endlessScrollListener)); From e9c1bf83187e7a34307f7ade869fdab95b9efb31 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 28 Nov 2016 17:59:58 +0100 Subject: [PATCH 39/92] Resolves #233 - Added comments to the methods --- .../flexibleadapter/FlexibleAdapter.java | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) 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 43eb2589..cc7fecd1 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -1745,34 +1745,85 @@ else if (elevation > 0)//Leave unaltered the default elevation /** * 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 set again the progressItem. + * 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; } - //TODO: add javaDocs + /** + * 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 ? getItemCount(false) / 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; } - public FlexibleAdapter setEndlessPageSize(@IntRange(from = 1) int endlessPageSize) { + /** + * 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; } - public FlexibleAdapter setEndlessTargetCount(@IntRange(from = 1) int endlessTargetCount) { + /** + * 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; } From 4a56e6cdf10d813211527b79fefcf45be86044dd Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 28 Nov 2016 20:29:41 +0100 Subject: [PATCH 40/92] Updated description and tags --- build.gradle | 2 +- jfrog-bintray-publish.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 92a4b752..96fcf2dc 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ ext { libraryCode = 19 libraryVersion = "5.0.0-SNAPSHOT" 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, Headers, Footers, EndlessScroll, Expandable, Draggable, Swipeable :-)" libraryName = "FlexibleAdapter" packageExt = "aar" diff --git a/jfrog-bintray-publish.gradle b/jfrog-bintray-publish.gradle index 421e5d48..cce64bde 100644 --- a/jfrog-bintray-publish.gradle +++ b/jfrog-bintray-publish.gradle @@ -50,7 +50,7 @@ bintray { websiteUrl = siteUrl licenses = allLicenses vcsUrl = gitUrl - labels = ['android', 'recyclerview', 'list', 'adapter', 'selection', 'actionmode', 'undo', 'viewholder', 'ripple', 'search', 'filter', 'filtering', 'async', 'asynchrnous', 'item', 'item animator', 'itemanimator', 'animator' ,'animation', 'expand', 'expandable', 'collapse', 'collapsible', 'section', 'sticky', 'header', 'category', 'drag', 'draggable', 'swipe', 'swipeable', 'leave behind', 'fastscroll', 'fastscroller', 'endless scroll', 'load more'] + labels = ['android', 'recyclerview', 'list', 'adapter', 'selection', 'actionmode', 'undo', 'viewholder', 'ripple', 'search', 'filter', 'filtering', 'async', 'asynchrnous', 'item', 'item animator', 'itemanimator', 'animator' ,'animation', 'expand', 'expandable', 'collapse', 'collapsible', 'section', 'sections', 'sticky', 'header', 'headers', 'category', 'footer', 'footers', 'drag', 'draggable', 'swipe', 'swipeable', 'leave behind', 'fastscroll', 'fastscroller', 'endless', 'endless scroll', 'load more'] publish = true publicDownloadNumbers = true //noinspection GroovyAssignabilityCheck From 8c0429b3c761a2838b12e84bc006946300c0cf22 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 28 Nov 2016 20:31:44 +0100 Subject: [PATCH 41/92] Updated deprecation comments --- .../davidea/flexibleadapter/FlexibleAdapter.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) 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 cc7fecd1..0c5326de 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -2716,10 +2716,7 @@ public boolean addSubItem(@IntRange(from = 0) int parentPosition, 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); + return addSubItems(parentPosition, subPosition, Collections.singletonList(item), expandParent, payload); } /** @@ -2741,6 +2738,7 @@ public boolean addSubItem(@IntRange(from = 0) int parentPosition, * @see #addSubItems(int, int, IExpandable, List, boolean, Object) * @since 5.0.0-b1 */ + //TODO: Deprecation, this is like a command to expand public int addAllSubItemsFrom(@IntRange(from = 0) int parentPosition, @NonNull IExpandable parent, boolean expandParent, @Nullable Object payload) { @@ -2774,7 +2772,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; } @@ -2809,7 +2807,7 @@ private boolean addSubItems(@IntRange(from = 0) int parentPosition, 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. + //If parent is collapsed there's no need to add sub items. if (parent.isExpanded()) { added = addItems(parentPosition + 1 + Math.max(0, subPosition), items); } @@ -3934,6 +3932,7 @@ public FlexibleAdapter setAnimateToLimit(int limit) { * @return true to calculate animation changes with DiffUtil, false to use default calculation. * @see #setAnimateChangesWithDiffUtil(boolean) */ + //TODO: Deprecation: DiffUtil is slower than the internal implementation public boolean isAnimateChangesWithDiffUtil() { return useDiffUtil; } @@ -3950,6 +3949,7 @@ public boolean isAnimateChangesWithDiffUtil() { * @return this Adapter, so the call can be chained * @see #setDiffUtilCallback(DiffUtilCallback) */ + //TODO: Deprecation: DiffUtil is slower than the internal implementation public FlexibleAdapter setAnimateChangesWithDiffUtil(boolean useDiffUtil) { this.useDiffUtil = useDiffUtil; return this; @@ -3963,11 +3963,13 @@ public FlexibleAdapter setAnimateChangesWithDiffUtil(boolean useDiffUtil) { * @return this Adapter, so the call can be chained * @see #setAnimateChangesWithDiffUtil(boolean) */ + //TODO: Deprecation: DiffUtil is slower than the internal implementation public FlexibleAdapter setDiffUtilCallback(DiffUtilCallback diffUtilCallback) { this.diffUtilCallback = diffUtilCallback; return this; } + //TODO: Deprecation: DiffUtil is slower than the internal implementation private synchronized void animateDiff(@Nullable List newItems, Payload payloadChange) { if (useDiffUtil) { Log.v(TAG, "Animate changes with DiffUtils! oldSize=" + getItemCount() + " newSize=" + newItems.size()); @@ -5224,6 +5226,7 @@ public boolean handleMessage(Message message) { *

    - {@code protected List oldItems;} *
    - {@code protected List newItems;} */ + //TODO: Deprecation: DiffUtil is slower than the internal implementation public static class DiffUtilCallback extends DiffUtil.Callback { protected List oldItems; From 5bf6eeb5fd47961ca3988d190b84fedeaa80d1f4 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 29 Nov 2016 00:49:09 +0100 Subject: [PATCH 42/92] Resolves #236 - Added support for Expandable Scrollable Headers and Footers --- .../flexibleadapter/ExampleAdapter.java | 12 +++++-- .../samples/flexibleadapter/MainActivity.java | 2 +- .../fragments/FragmentEndlessScrolling.java | 8 +++-- .../fragments/FragmentViewPager.java | 6 ++-- .../flexibleadapter/items/SimpleItem.java | 20 +++++++----- .../src/main/res/drawable/myrect.xml | 5 +++ .../res/layout/fragment_recycler_view.xml | 4 +-- .../main/res/layout/fragment_view_pager.xml | 2 +- .../res/layout/recycler_expandable_item.xml | 3 +- .../main/res/layout/recycler_header_item.xml | 3 +- .../recycler_scrollable_footer_item.xml | 4 +-- .../layout/recycler_scrollable_uls_item.xml | 4 +-- .../flexibleadapter/FlexibleAdapter.java | 31 ++++++++++++++++--- 13 files changed, 73 insertions(+), 31 deletions(-) create mode 100644 flexible-adapter-app/src/main/res/drawable/myrect.xml 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 36d6e96c..4eb877a5 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 @@ -54,6 +54,15 @@ public void updateDataSet(List items, boolean animate) { // 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 shows how to add Header View as it was for ListView. @@ -61,7 +70,7 @@ public void updateDataSet(List items, boolean animate) { * The view is represented by a custom Item type to better represent any dynamic content. */ public void showLayoutInfo() { - if (!hasSearchText() && !isEmpty()) { + if (!hasSearchText()) { //Define Example View final ScrollableLayoutItem item = new ScrollableLayoutItem("LAY-L"); if (mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) { @@ -152,7 +161,6 @@ public void addScrollableFooter() { // public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { // //Not implemented: METHOD A is used // } - @Override public String onCreateBubbleText(int position) { if (!DatabaseConfiguration.userLearnedSelection && position == 0) {//This 'if' is for my example only 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 270ddd45..650315b8 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 @@ -262,7 +262,7 @@ private void initializeSwipeToRefresh() { //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); 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 9a83f45a..4e3964b1 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 @@ -108,7 +108,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { // EndlessScrollListener - OnLoadMore (v5.0.0) mAdapter.setEndlessScrollListener(this, new ProgressItem()) - .setEndlessPageSize(3) +// .setEndlessPageSize(3) .setEndlessTargetCount(15); // .setEndlessScrollThreshold(1); //Default=1 @@ -116,6 +116,8 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mAdapter.addUserLearnedSelection(savedInstanceState == null); mAdapter.showLayoutInfo(); mAdapter.addScrollableFooter(); + mAdapter.addScrollableHeaderWithDelay(DatabaseService.newExpandableSectionItem(111), 5000L, true); + mAdapter.addScrollableFooterWithDelay(DatabaseService.newExpandableSectionItem(999), 5000L, false); } @Override @@ -170,7 +172,7 @@ public void run() { final List newItems = new ArrayList<>(); // Simulating success/failure with Random - int count = new Random().nextInt(10); + int count = new Random().nextInt(7); int totalItemsOfType = mAdapter.getItemCountOfTypes(R.layout.recycler_expandable_item); for (int i = 1; i <= count; i++) { newItems.add(DatabaseService.newSimpleItem(totalItemsOfType + i, null)); @@ -179,7 +181,7 @@ public void run() { // 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 - mAdapter.onLoadMoreComplete(newItems); + mAdapter.onLoadMoreComplete(newItems, 5000L); DatabaseService.getInstance().addAll(newItems); // It's better to read the page after adding new items! Log.d(TAG, "EndlessCurrentPage=" + mAdapter.getEndlessCurrentPage()); 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 index df929704..42ff14ef 100644 --- 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 @@ -81,13 +81,13 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) { FlipView.resetLayoutAnimationDelay(true, 1000L); //Initialize RecyclerView - initializeRecyclerView(savedInstanceState); + initializeRecyclerView(); //Settings for FlipView FlipView.stopLayoutAnimation(); } - private void initializeRecyclerView(Bundle savedInstanceState) { + 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); @@ -108,7 +108,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mAdapter.toggleFastScroller(); //Sticky Headers - mAdapter.setStickyHeaderContainer((ViewGroup) getView().findViewById(R.id.sticky_header_container)) + mAdapter//.setStickyHeaderContainer((ViewGroup) getView().findViewById(R.id.sticky_header_container)) .setDisplayHeadersAtStartUp(true) .enableStickyHeaders(); } diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java index e45d17bc..e2a07673 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java @@ -2,6 +2,7 @@ import android.animation.Animator; import android.content.Context; +import android.graphics.Color; import android.support.annotation.NonNull; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.StaggeredGridLayoutManager; @@ -21,6 +22,7 @@ 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; @@ -41,7 +43,7 @@ public class SimpleItem extends AbstractItem */ HeaderItem header; - public SimpleItem(String id) { + SimpleItem(String id) { super(id); setDraggable(true); setSwipeable(true); @@ -89,6 +91,8 @@ public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holde + (getUpdates() > 0 ? " - u" + getUpdates() : "")); } + DrawableUtils.setBackground(holder.frontView, DrawableUtils.getSelectableBackgroundCompat( + Color.LTGRAY, Color.WHITE, Color.LTGRAY)); Context context = holder.itemView.getContext(); int defColorAccent = context.getResources().getColor(R.color.colorAccent_light); @@ -145,16 +149,16 @@ public boolean filter(String constraint) { */ static final class ParentViewHolder extends ExpandableViewHolder { - public FlipView mFlipView; - public TextView mTitle; - public TextView mSubtitle; - public ImageView mHandleView; - public Context mContext; - private View frontView; + FlipView mFlipView; + TextView mTitle; + TextView mSubtitle; + ImageView mHandleView; + Context mContext; + View frontView; private View rearLeftView; private View rearRightView; - public ParentViewHolder(View view, FlexibleAdapter adapter) { + ParentViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); this.mContext = view.getContext(); this.mTitle = (TextView) view.findViewById(R.id.title); 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/fragment_recycler_view.xml b/flexible-adapter-app/src/main/res/layout/fragment_recycler_view.xml index 88adac21..31f8921b 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,7 +10,7 @@ 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 index fb93dc06..317452bd 100644 --- a/flexible-adapter-app/src/main/res/layout/fragment_view_pager.xml +++ b/flexible-adapter-app/src/main/res/layout/fragment_view_pager.xml @@ -10,7 +10,7 @@ - + - + From f8ec700d5093b656f544446c6a440e8367b61373 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 29 Nov 2016 11:27:32 +0100 Subject: [PATCH 44/92] Resolves #236 - Disallowed dragging for Scrollable Headers and Footers, main items cannot swap too. --- .../eu/davidea/flexibleadapter/FlexibleAdapter.java | 11 +++++++---- .../eu/davidea/viewholders/FlexibleViewHolder.java | 9 ++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) 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 fe6c9c67..21196ef7 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -743,7 +743,7 @@ public final List getScrollableFooters() { *

    Scrollable Headers have the following characteristic: *

      *
    • lay always before any main item.
    • - *
    • cannot be selectable.
    • + *
    • 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.
    • @@ -762,6 +762,7 @@ 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); performInsert(progressFix, Collections.singletonList(headerItem), true); @@ -777,7 +778,7 @@ public final boolean addScrollableHeader(@NonNull T headerItem) { *

      Scrollable Footers have the following characteristic: *

        *
      • lay always after any main item.
      • - *
      • cannot be selectable.
      • + *
      • cannot be selectable nor draggable.
      • *
      • cannot be inserted twice, but many can be inserted.
      • *
      • 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.
      • @@ -798,6 +799,7 @@ 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) { @@ -4506,8 +4508,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)); } /** diff --git a/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java b/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java index b8ccfbeb..b3dddce4 100644 --- a/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java +++ b/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java @@ -160,7 +160,10 @@ public boolean onLongClick(View view) { @Override public boolean onTouch(View view, MotionEvent event) { int position = getFlexibleAdapterPosition(); - if (!mAdapter.isEnabled(position)) return false; + if (!mAdapter.isEnabled(position) || !isDraggable()) { + Log.w(TAG, "Can't start drag: Item is not enabled or draggable!"); + return false; + } if (FlexibleAdapter.DEBUG) Log.v(TAG, "onTouch with DragHandleView on position " + position + " mode=" + mAdapter.getMode()); if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN && @@ -364,7 +367,7 @@ public void onItemReleased(int position) { * @since 5.0.0-b7 */ @Override - public boolean isDraggable() { + public final boolean isDraggable() { IFlexible item = mAdapter.getItem(getFlexibleAdapterPosition()); return item != null && item.isDraggable(); } @@ -374,7 +377,7 @@ public boolean isDraggable() { * @since 5.0.0-b7 */ @Override - public boolean isSwipeable() { + public final boolean isSwipeable() { IFlexible item = mAdapter.getItem(getFlexibleAdapterPosition()); return item != null && item.isSwipeable(); } From a822cc17bdb432a16f205c86be6837282581f8b4 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 29 Nov 2016 14:11:46 +0100 Subject: [PATCH 45/92] Resolves #201 - Added method shouldNotifyParentOnClick() --- .../eu/davidea/flexibleadapter/Payload.java | 10 ++-- .../viewholders/ExpandableViewHolder.java | 48 +++++++++++++++---- 2 files changed, 46 insertions(+), 12 deletions(-) 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/viewholders/ExpandableViewHolder.java b/flexible-adapter/src/main/java/eu/davidea/viewholders/ExpandableViewHolder.java index 9f6cfdc1..490f5f45 100644 --- a/flexible-adapter/src/main/java/eu/davidea/viewholders/ExpandableViewHolder.java +++ b/flexible-adapter/src/main/java/eu/davidea/viewholders/ExpandableViewHolder.java @@ -21,11 +21,13 @@ import android.view.View.OnLongClickListener; import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.Payload; /** - * ViewHolder for a Expandable Items. Holds callbacks which can be used to trigger expansion events. - *

        This class extends {@link FlexibleViewHolder}, which means it will benefit of all implemented - * methods the super class holds.

        + * ViewHolder for a Expandable Items. It holds methods which can be used to trigger expansion + * or collapsing events. + *

        This class extends {@link FlexibleViewHolder}, which means it will benefit of all + * implemented methods the super class holds.

        * * @author Davide Steduto * @since 16/01/2016 Created @@ -66,7 +68,7 @@ public ExpandableViewHolder(View view, FlexibleAdapter adapter, boolean stickyHe /*--------------*/ /** - * Allows to expand or collapse child views of this ItemView when {@link OnClickListener} + * Allows to expand or collapse child views of this itemView when {@link 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.

        @@ -91,9 +93,28 @@ protected boolean isViewCollapsibleOnLongClick() { return 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 + */ + protected boolean shouldNotifyParentOnClick() { + return false; + } + /** * Expands or Collapses based on the current state. * + * @see #shouldNotifyParentOnClick() + * @see #expandView(int) + * @see #collapseView(int) * @since 5.0.0-b1 */ @CallSuper @@ -107,23 +128,31 @@ protected void toggleExpansion() { } /** - * Triggers expansion of the Item. + * 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 */ @CallSuper protected void expandView(int position) { mAdapter.expand(position); + if (shouldNotifyParentOnClick()) mAdapter.notifyItemChanged(position, Payload.EXPANDED); } /** - * Triggers collapse of the Item. + * 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 */ @CallSuper protected void collapseView(int position) { mAdapter.collapse(position); + if (shouldNotifyParentOnClick()) mAdapter.notifyItemChanged(position, Payload.COLLAPSED); } /*---------------------------------*/ @@ -135,7 +164,7 @@ protected void collapseView(int position) { *

        Note: In Expandable version, it tries to expand, but before, * it checks if the view {@link #isViewExpandableOnClick()}.

        * - * @param view the View that is the trigger for expansion + * @param view the view that receives the event * @since 5.0.0-b1 */ @Override @@ -147,11 +176,11 @@ public void onClick(View view) { } /** - * Called when user long taps on the ItemView. + * Called when user long taps on this itemView. *

        Note: In Expandable version, it tries to collapse, but before, * it checks if the view {@link #isViewCollapsibleOnLongClick()}.

        * - * @param view the View that is the trigger for collapsing + * @param view the view that receives the event * @since 5.0.0-b1 */ @Override @@ -166,6 +195,7 @@ public boolean onLongClick(View view) { /** * {@inheritDoc} *

        Note: In the Expandable version, expanded items are forced to collapse.

        + * * @since 5.0.0-b1 */ @Override From 11475e919f6245ca99df9c53da3c9ae8d58436e2 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 29 Nov 2016 14:33:46 +0100 Subject: [PATCH 46/92] Updated javaDoc descriptions and changed some methods signature to "final" --- .../viewholders/ContentViewHolder.java | 12 +-- .../viewholders/FlexibleViewHolder.java | 80 +++++++++++-------- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java b/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java index 6df523a0..e8488f25 100644 --- a/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java +++ b/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java @@ -41,14 +41,14 @@ abstract class ContentViewHolder extends RecyclerView.ViewHolder { * @param stickyHeader true if the ViewHolder is a header to be sticky * @since 5.0.0-b7 */ - public ContentViewHolder(View view, FlexibleAdapter adapter, boolean stickyHeader) { - //Since itemView is declared "final", the split is done before the View is initialized + ContentViewHolder(View view, FlexibleAdapter adapter, boolean stickyHeader) { + // Since itemView is declared "final", the split is done before the View is initialized super(stickyHeader ? new FrameLayout(view.getContext()) : view); if (stickyHeader) { itemView.setLayoutParams(adapter.getRecyclerView().getLayoutManager() .generateLayoutParams(view.getLayoutParams())); - ((FrameLayout) itemView).addView(view);//Add View after setLayoutParams + ((FrameLayout) itemView).addView(view); //Add View after setLayoutParams contentView = view; } } @@ -64,7 +64,7 @@ public ContentViewHolder(View view, FlexibleAdapter adapter, boolean stickyHeade * @return the real contentView * @since 5.0.0-b7 */ - public View getContentView() { + public final View getContentView() { return contentView != null ? contentView : itemView; } @@ -79,7 +79,7 @@ public View getContentView() { * @see #setBackupPosition(int) * @since 5.0.0-b6 */ - public int getFlexibleAdapterPosition() { + public final int getFlexibleAdapterPosition() { int position = getAdapterPosition(); if (position == RecyclerView.NO_POSITION) { position = mBackupPosition; @@ -94,7 +94,7 @@ public int getFlexibleAdapterPosition() { * @param backupPosition the known position of this ViewHolder * @since 5.0.0-b6 */ - public void setBackupPosition(int backupPosition) { + public final void setBackupPosition(int backupPosition) { mBackupPosition = backupPosition; } diff --git a/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java b/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java index b3dddce4..2685202b 100644 --- a/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java +++ b/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java @@ -104,6 +104,7 @@ public FlexibleViewHolder(View view, FlexibleAdapter adapter, boolean stickyHead /** * {@inheritDoc} * + * @see #toggleActivation() * @since 5.0.0-b1 */ @Override @@ -130,6 +131,7 @@ public void onClick(View view) { /** * {@inheritDoc} * + * @see #toggleActivation() * @since 5.0.0-b1 */ @Override @@ -179,7 +181,7 @@ public boolean onTouch(View view, MotionEvent event) { /*--------------*/ /** - * Sets the inner view which will be used to drag the Item ViewHolder. + * Sets the inner view which will be used to drag this itemView. * * @param view handle view * @see #onTouch(View, MotionEvent) @@ -192,19 +194,23 @@ protected void setDragHandleView(@NonNull View view) { } /** - * Allows to change and see the activation status on the ItemView and to perform object - * animation in it. - *

        IMPORTANT NOTE! the change of the background is visible if you added - * android:background="?attr/selectableItemBackground" on the item layout AND - * in the style.xml.
        - * Adapter must have a reference to its instance to check selection state.

        - *

        This must be called every time we want the activation state visible on the ItemView, - * for instance, after a Click (to add the item to the selection list) or after a LongClick - * (to activate the ActionMode) or during a Drag (to show that we enabled the Drag).

        - * If you do this, it's not necessary to invalidate the row (with notifyItemChanged): - * In this way bindViewHolder is NOT called and inner Views can animate without - * interruption, so you can see the animation running still having the selection activated. + * Allows to change and see the activation status on the itemView and to perform animation + * on inner views. + *

        IMPORTANT NOTE! the selected background is visible if you added + * {@code android:background="?attr/selectableItemBackground"} on the item layout AND + * customized the file {@code style.xml}.

        + * Alternatively, to set a background at runtime, you can use the new + * {@link eu.davidea.flexibleadapter.utils.DrawableUtils}. + *

        Note: This method must be called every time we want the activation state visible + * on the itemView, for instance: after a Click (to add the item to the selection list) or + * after a LongClick (to activate the ActionMode) or during dragging (to show that we enabled + * the Drag).

        + * If you follow the above instructions, it's not necessary to invalidate this view with + * {@code notifyItemChanged}: In this way {@code bindViewHolder} won't be called and inner + * views can animate without interruptions, eventually you will see the animation running + * on those inner views at the same time of selection activation. * + * @see #getActivationElevation() * @since 5.0.0-b1 */ @CallSuper @@ -212,15 +218,17 @@ protected void toggleActivation() { itemView.setActivated(mAdapter.isSelected(getFlexibleAdapterPosition())); if (itemView.isActivated() && getActivationElevation() > 0) ViewCompat.setElevation(itemView, getActivationElevation()); - else if (getActivationElevation() > 0)//Leave unaltered the default elevation + else if (getActivationElevation() > 0) //Leave unaltered the default elevation ViewCompat.setElevation(itemView, 0); } /** * Allows to set elevation while the view is activated. *

        Override to return desired value of elevation on this itemView.

        + * Note: returned value must be in Pixel. * - * @return never elevate, returns 0dp if not overridden + * @return {@code 0px} (never elevate) if not overridden + * @see #toggleActivation() * @since 5.0.0-b2 */ public float getActivationElevation() { @@ -229,10 +237,11 @@ public float getActivationElevation() { /** * Allows to activate the itemView when Swipe event occurs. - *

        This method returns always false; Extend with "return true" to Not expand or collapse - * this itemView onClick events.

        + *

        This method returns always false; Override with {@code "return true"} to Not expand or + * collapse this itemView onClick events.

        * * @return always false, if not overridden + * @see #toggleActivation() * @since 5.0.0-b2 */ protected boolean shouldActivateViewWhileSwiping() { @@ -241,10 +250,11 @@ protected boolean shouldActivateViewWhileSwiping() { /** * Allows to add and keep item selection if ActionMode is active. - *

        This method returns always false; Extend with "return true" to add the item to the - * ActionMode count.

        + *

        This method returns always false;Override with {@code "return true"} to add the item + * to the ActionMode count.

        * * @return always false, if not overridden + * @see #toggleActivation() * @since 5.0.0-b2 */ protected boolean shouldAddSelectionInActionMode() { @@ -266,11 +276,11 @@ protected boolean shouldAddSelectionInActionMode() { * @param animators NonNull list of animators, which you should add new animators * @param position can be used to differentiate the Animators based on positions * @param isForward can be used to separate animation from top/bottom or from left/right scrolling - * @since 5.0.0-b8 * @see AnimatorHelper + * @since 5.0.0-b8 */ public void scrollAnimators(@NonNull List animators, int position, boolean isForward) { - //Free to implement + // Free to implement } /*--------------------------------*/ @@ -278,14 +288,16 @@ public void scrollAnimators(@NonNull List animators, int position, boo /*--------------------------------*/ /** - * Here we handle the event of when the ItemTouchHelper first registers an item as being - * moved or swiped. - *

        In this implementation, View activation is automatically handled in case of Drag: - * The Item will be added to the selection list if not selected yet and mode MULTI is activated.

        + * Here we handle the event of when the {@code ItemTouchHelper} first registers an item + * as being moved or swiped. + *

        In this implementation, View activation is automatically handled if dragged: The Item + * will be added to the selection list if not selected yet and mode MULTI is activated.

        * * @param position the position of the item touched * @param actionState one of {@link ItemTouchHelper#ACTION_STATE_SWIPE} or * {@link ItemTouchHelper#ACTION_STATE_DRAG}. + * @see #shouldActivateViewWhileSwiping() + * @see #shouldAddSelectionInActionMode() * @since 5.0.0-b1 */ @Override @@ -298,23 +310,23 @@ public void onActionStateChanged(int position, int actionState) { " actionState=" + (actionState == ItemTouchHelper.ACTION_STATE_SWIPE ? "Swipe(1)" : "Drag(2)")); if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { if (!alreadySelected) { - //Be sure, if MODE_MULTI is active, to add this item to the selection list (call listener!) - //Also be sure user consumes the long click event if not done in onLongClick. - //Drag by LongPress or Drag by handleView + // Be sure, if MODE_MULTI is active, to add this item to the selection list (call listener!) + // Also be sure user consumes the long click event if not done in onLongClick. + // Drag by LongPress or Drag by handleView if (mLongClickSkipped || mAdapter.getMode() == SelectableAdapter.MODE_MULTI) { - //Next check, allows to initiate the ActionMode and to add selection if configured + // Next check, allows to initiate the ActionMode and to add selection if configured if ((shouldAddSelectionInActionMode() || mAdapter.getMode() != SelectableAdapter.MODE_MULTI) && mAdapter.mItemLongClickListener != null && mAdapter.isSelectable(position)) { mAdapter.mItemLongClickListener.onItemLongClick(position); alreadySelected = true; //Keep selection on release! } } - //If still not selected, be sure current item appears selected for the Drag transition + // If still not selected, be sure current item appears selected for the Drag transition if (!alreadySelected) { mAdapter.toggleSelection(position); } } - //Now toggle the activation, Activate view and make selection visible only if necessary + // Now toggle the activation, Activate view and make selection visible only if necessary if (!itemView.isActivated()) { toggleActivation(); } @@ -331,6 +343,8 @@ public void onActionStateChanged(int position, int actionState) { * In case of Drag, the state will be cleared depends by current selection mode! * * @param position the position of the item released + * @see #shouldActivateViewWhileSwiping() + * @see #shouldAddSelectionInActionMode() * @since 5.0.0-b1 */ @Override @@ -339,7 +353,7 @@ public void onItemReleased(int position) { if (FlexibleAdapter.DEBUG) Log.v(TAG, "onItemReleased position=" + position + " mode=" + mAdapter.getMode() + " actionState=" + (mActionState == ItemTouchHelper.ACTION_STATE_SWIPE ? "Swipe(1)" : "Drag(2)")); - //Be sure to keep selection if MODE_MULTI and shouldAddSelectionInActionMode is active + // Be sure to keep selection if MODE_MULTI and shouldAddSelectionInActionMode is active if (!alreadySelected) { if (shouldAddSelectionInActionMode() && mAdapter.getMode() == SelectableAdapter.MODE_MULTI) { @@ -357,7 +371,7 @@ public void onItemReleased(int position) { } } } - //Reset internal action state ready for next action + // Reset internal action state ready for next action mLongClickSkipped = false; mActionState = ItemTouchHelper.ACTION_STATE_IDLE; } From 135d6e008fcf311a299e336e89345f024c4129e4 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 29 Nov 2016 17:59:05 +0100 Subject: [PATCH 47/92] Modified demoApp to showcase Scrollable Expandable Headers and Footers --- .../flexibleadapter/ExampleAdapter.java | 82 +++++++++---- .../fragments/FragmentEndlessScrolling.java | 41 +++---- .../flexibleadapter/items/AbstractItem.java | 1 + .../items/AnimatorSubItem.java | 2 +- .../items/ExpandableHeaderItem.java | 95 +++++++++++++-- .../flexibleadapter/items/ExpandableItem.java | 4 +- .../items/ExpandableLevel0Item.java | 2 - .../items/ExpandableLevel1Item.java | 2 - .../flexibleadapter/items/HeaderItem.java | 4 +- .../items/ScrollableExpandableItem.java | 112 ++++++++++++++++++ .../items/ScrollableFooterItem.java | 12 +- .../items/ScrollableLayoutItem.java | 22 +--- .../items/ScrollableSubItem.java | 69 +++++++++++ .../items/ScrollableULSItem.java | 22 +--- .../items/ScrollableUseCaseItem.java | 19 ++- .../flexibleadapter/items/SimpleItem.java | 2 - .../flexibleadapter/items/SubItem.java | 2 - .../recycler_scrollable_expandable_item.xml | 72 +++++++++++ .../recycler_scrollable_footer_item.xml | 3 +- .../recycler_scrollable_header_item.xml | 7 +- .../recycler_scrollable_layout_item.xml | 4 +- .../layout/recycler_scrollable_sub_item.xml | 19 +++ .../layout/recycler_scrollable_uls_item.xml | 1 + .../src/main/res/values/strings.xml | 4 + .../flexibleadapter/FlexibleAdapter.java | 1 + .../items/AbstractFlexibleItem.java | 2 +- 26 files changed, 470 insertions(+), 136 deletions(-) create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableExpandableItem.java create mode 100644 flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableSubItem.java create mode 100644 flexible-adapter-app/src/main/res/layout/recycler_scrollable_expandable_item.xml create mode 100644 flexible-adapter-app/src/main/res/layout/recycler_scrollable_sub_item.xml 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 4eb877a5..bd3933ce 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,5 +1,6 @@ package eu.davidea.samples.flexibleadapter; +import android.content.Context; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -11,8 +12,10 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; +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; @@ -31,6 +34,7 @@ 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) { //stableIds ? true = Items implement hashCode() so they can have stableIds! @@ -43,20 +47,20 @@ public ExampleAdapter(List items, Object listeners) { @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); - //onPostUpdate() will be automatically called at the end of the Asynchronous update process. - // Manipulate the list inside that method only or you won't see the changes. + // 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. + * You can override this method to define your own concept of "Empty". + * This method is never called internally. */ @Override public boolean isEmpty() { @@ -71,7 +75,6 @@ public boolean isEmpty() { */ public void showLayoutInfo() { if (!hasSearchText()) { - //Define Example View final ScrollableLayoutItem item = new ScrollableLayoutItem("LAY-L"); if (mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) { item.setId("LAY-S"); @@ -93,12 +96,11 @@ public void showLayoutInfo() { /* * ANOTHER HEADER VIEW - * This method shows how to add a Header View with a delay. + * 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 ScrollableULSItem)) { - //Define Example View final ScrollableULSItem item = new ScrollableULSItem("ULS"); item.setTitle(mRecyclerView.getContext().getString(R.string.uls_title)); item.setSubtitle(mRecyclerView.getContext().getString(R.string.uls_subtitle)); @@ -108,17 +110,40 @@ public void addUserLearnedSelection(boolean scrollToPosition) { /* * FOOTER VIEW - * This method shows how to delay add a 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() { - //Define Example View 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). @@ -129,9 +154,9 @@ public void addScrollableFooter() { // public ViewGroup getStickySectionHeadersHolder() { // 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); // } @@ -150,7 +175,7 @@ public void addScrollableFooter() { */ // @Override // public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { -// //Not implemented: METHOD A is used +// // Not implemented: METHOD A is used // } /** @@ -159,19 +184,24 @@ public void addScrollableFooter() { */ // @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 + * 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)}. @@ -185,14 +215,14 @@ private class MyHandlerCallback extends HandlerCallback { public boolean handleMessage(Message message) { boolean done = super.handleMessage(message); switch (message.what) { - //currently reserved - case 0://async updateDataSet - case 1://async filterItems - case 2://confirm delete - case 8://onLoadMore remove progress item + // 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 + // Free to use, example: case 10: case 11: return true; 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 4e3964b1..d268559f 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 @@ -66,15 +66,17 @@ public void onActivityCreated(Bundle savedInstanceState) { FlipView.resetLayoutAnimationDelay(true, 1000L); // Create New Database and Initialize RecyclerView - DatabaseService.getInstance().createEndlessDatabase(0); //N. of items - initializeRecyclerView(savedInstanceState); + if (DatabaseService.getInstance().getDatabaseList().size() == 0) { + DatabaseService.getInstance().createEndlessDatabase(0); //N. of items + } + initializeRecyclerView(); // Settings for FlipView FlipView.stopLayoutAnimation(); } @SuppressWarnings({"ConstantConditions", "NullableProblems"}) - private void initializeRecyclerView(Bundle savedInstanceState) { + private void initializeRecyclerView() { // Initialize Adapter and RecyclerView // ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()' mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity()); @@ -112,12 +114,9 @@ private void initializeRecyclerView(Bundle savedInstanceState) { .setEndlessTargetCount(15); // .setEndlessScrollThreshold(1); //Default=1 - // Add 2 Scrollable Headers and 1 Footer items - mAdapter.addUserLearnedSelection(savedInstanceState == null); + // Add 1 Scrollable Header and 1 Footer items mAdapter.showLayoutInfo(); mAdapter.addScrollableFooter(); - mAdapter.addScrollableHeaderWithDelay(DatabaseService.newExpandableSectionItem(111), 5000L, true); - mAdapter.addScrollableFooterWithDelay(DatabaseService.newExpandableSectionItem(999), 5000L, false); } @Override @@ -126,7 +125,6 @@ public void showNewLayoutInfo(MenuItem item) { mAdapter.showLayoutInfo(); } - /** * No more data to load. *

        This method is called if any limit is reached (targetCount or pageSize @@ -171,43 +169,45 @@ public void onLoadMore(int lastPosition, int currentPage) { public void run() { final List newItems = new ArrayList<>(); - // Simulating success/failure with Random + // 1. Simulating success/failure with Random int count = new Random().nextInt(7); int totalItemsOfType = mAdapter.getItemCountOfTypes(R.layout.recycler_expandable_item); for (int i = 1; i <= count; i++) { newItems.add(DatabaseService.newSimpleItem(totalItemsOfType + i, null)); } - // Callback the Adapter to notify the change: + // 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 mAdapter.onLoadMoreComplete(newItems, 5000L); DatabaseService.getInstance().addAll(newItems); - // It's better to read the page after adding new items! + // - 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 + // 4. Notify user if (getActivity() != null && newItems.size() > 0) { Toast.makeText(getActivity(), "Simulated: " + newItems.size() + " new items arrived :-)", Toast.LENGTH_SHORT).show(); } } - }, 2000); + }, 5000L); } @Override @@ -248,6 +248,7 @@ public int getSpanSize(int position) { // 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_scrollable_expandable_item: case R.layout.recycler_scrollable_header_item: case R.layout.recycler_scrollable_footer_item: case R.layout.recycler_scrollable_layout_item: diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java index 9171bef4..0d65a1f0 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java @@ -15,6 +15,7 @@ public abstract class AbstractItem protected String id; protected String title; protected String subtitle = ""; + /* number of times this item has been refreshed */ protected int updates; public AbstractItem(String id) { diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AnimatorSubItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AnimatorSubItem.java index 63f9553b..4024ebd7 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AnimatorSubItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AnimatorSubItem.java @@ -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/items/ExpandableHeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableHeaderItem.java index 67c391b9..1683e4ba 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableHeaderItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableHeaderItem.java @@ -12,6 +12,7 @@ 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; @@ -28,8 +29,6 @@ public class ExpandableHeaderItem implements IExpandable, IHeader { - private static final long serialVersionUID = -1882711111814491060L; - /* Flags for FlexibleAdapter */ private boolean mExpanded = false; @@ -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 index 7e6d775f..e1c7b39f 100644 --- 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 @@ -13,8 +13,6 @@ public class ExpandableItem extends SimpleItem implements IExpandable { - private static final long serialVersionUID = -6882745111884490060L; - /* Flags for FlexibleAdapter */ private boolean mExpanded = false; @@ -68,7 +66,7 @@ public boolean removeSubItem(int position) { public void addSubItem(SubItem subItem) { if (mSubItems == null) - mSubItems = new ArrayList(); + mSubItems = new ArrayList<>(); mSubItems.add(subItem); } diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel0Item.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel0Item.java index 12c6ee2d..41e6612b 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel0Item.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel0Item.java @@ -29,8 +29,6 @@ public class ExpandableLevel0Item extends AbstractItem implements IExpandable, IHeader { - private static final long serialVersionUID = -1882711111814491060L; - /* Flags for FlexibleAdapter */ private boolean mExpanded = false; diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java index 4be91d22..b10bc268 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java @@ -20,8 +20,6 @@ public class ExpandableLevel1Item extends AbstractItem implements IExpandable { - private static final long serialVersionUID = -1882711111814491060L; - /* Flags for FlexibleAdapter */ private boolean mExpanded = false; diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java index 8a069672..31484fdd 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java @@ -24,8 +24,6 @@ */ public class HeaderItem extends AbstractHeaderItem implements IFilterable { - private static final long serialVersionUID = -7408637077727563374L; - private String id; private String title; private String subtitle; @@ -109,7 +107,7 @@ static class HeaderViewHolder extends FlexibleViewHolder { 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/items/ScrollableExpandableItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableExpandableItem.java new file mode 100644 index 00000000..03e84e61 --- /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 "LayoutItem[" + 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 index 61fb823b..a2a53166 100644 --- 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 @@ -5,7 +5,6 @@ import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorListener; import android.support.v7.widget.StaggeredGridLayoutManager; -import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -18,6 +17,7 @@ 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; @@ -30,11 +30,6 @@ public ScrollableFooterItem(String id) { super(id); } - @Override - public boolean isSelectable() { - return false; - } - @Override public int getLayoutRes() { return R.layout.recycler_scrollable_footer_item; @@ -47,9 +42,8 @@ public FooterViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater @Override public void bindViewHolder(FlexibleAdapter adapter, FooterViewHolder holder, int position, List payloads) { -// holder.mTitle.setSelected(true);//For marquee - holder.mTitle.setText(Html.fromHtml(getTitle())); - holder.mSubtitle.setText(Html.fromHtml(getSubtitle())); + holder.mTitle.setText(Utils.fromHtmlCompat(getTitle())); + holder.mSubtitle.setText(Utils.fromHtmlCompat(getSubtitle())); } class FooterViewHolder extends FlexibleViewHolder implements AnimatedViewHolder { 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 index f184dbfa..92ee873e 100644 --- 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 @@ -14,15 +14,12 @@ 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. - * - *

        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 ScrollableLayoutItem extends AbstractItem { @@ -30,16 +27,6 @@ public ScrollableLayoutItem(String id) { super(id); } - @Override - public boolean isEnabled() { - return false; - } - - @Override - public boolean isSelectable() { - return false; - } - @Override public int getLayoutRes() { return R.layout.recycler_scrollable_layout_item; @@ -52,14 +39,13 @@ public LayoutViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater @Override public void bindViewHolder(FlexibleAdapter adapter, LayoutViewHolder holder, int position, List payloads) { - holder.mTitle.setSelected(true);//For marquee - holder.mTitle.setText(getTitle()); - holder.mSubtitle.setText(getSubtitle()); + 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("LayoutItem", "LayoutItem configured fullSpan for StaggeredGridLayout"); + Log.d("ScrollableLayoutItem", "LayoutItem configured fullSpan for StaggeredGridLayout"); } } 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..47ab65e5 --- /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.getRelativePositionOf(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/items/ScrollableULSItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java index 73ffd6f9..179a5f4e 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java @@ -3,7 +3,6 @@ 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,13 +15,12 @@ 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).

        + * This item is a Scrollable Header. */ public class ScrollableULSItem extends AbstractItem { @@ -30,16 +28,6 @@ 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_scrollable_uls_item; @@ -54,9 +42,9 @@ public ULSViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater in 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())); } /** 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 index af4aa1b2..3c48672f 100644 --- 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 @@ -3,7 +3,6 @@ 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; @@ -15,12 +14,13 @@ 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; /** * This item is a Scrollable Header. */ -public class ScrollableUseCaseItem extends AbstractItem { +public class ScrollableUseCaseItem extends AbstractItem { public ScrollableUseCaseItem(String id) { super(id); @@ -37,24 +37,23 @@ public int getLayoutRes() { } @Override - public HeaderViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) { - return new HeaderViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter); + public UCViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) { + return new UCViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter); } @Override - public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { -// holder.mTitle.setSelected(true);//For marquee - holder.mTitle.setText(Html.fromHtml(getTitle())); - holder.mSubtitle.setText(Html.fromHtml(getSubtitle())); + public void bindViewHolder(FlexibleAdapter adapter, UCViewHolder holder, int position, List payloads) { + holder.mTitle.setText(Utils.fromHtmlCompat(getTitle())); + holder.mSubtitle.setText(Utils.fromHtmlCompat(getSubtitle())); } - class HeaderViewHolder extends FlexibleViewHolder { + class UCViewHolder extends FlexibleViewHolder { TextView mTitle; TextView mSubtitle; ImageView mDismissIcon; - public HeaderViewHolder(View view, FlexibleAdapter adapter) { + UCViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); 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/items/SimpleItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java index e2a07673..9c44e341 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java @@ -36,8 +36,6 @@ public class SimpleItem extends AbstractItem implements ISectionable, IFilterable, Serializable { - private static final long serialVersionUID = -6882745111884490060L; - /** * The header of this item */ diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SubItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SubItem.java index 8e802f9c..5576859a 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SubItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SubItem.java @@ -28,8 +28,6 @@ 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/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 index 6eecda02..718b5708 100644 --- 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 @@ -45,13 +45,14 @@ android:layout_toStartOf="@id/image_util_container" android:orientation="vertical"> + 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 index 32139f76..f08a76cc 100644 --- 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 @@ -15,8 +15,8 @@ android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_centerInParent="true" - android:layout_marginEnd="@dimen/margin_small" - android:layout_marginRight="@dimen/margin_small"> + android:layout_marginEnd="@dimen/margin_right" + android:layout_marginRight="@dimen/margin_right"> + diff --git a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_layout_item.xml b/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_scrollable_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_scrollable_uls_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_uls_item.xml index 7cba0f8c..405c8588 100644 --- a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_uls_item.xml +++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_uls_item.xml @@ -58,6 +58,7 @@ android:layout_centerInParent="true" android:orientation="vertical"> + This is 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. 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 21196ef7..c9138a71 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -1551,6 +1551,7 @@ private boolean linkHeaderTo(@NonNull T item, @NonNull IHeader header, @Nullable } 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); diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/items/AbstractFlexibleItem.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/items/AbstractFlexibleItem.java index 46d54fe8..44b2c637 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/items/AbstractFlexibleItem.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/items/AbstractFlexibleItem.java @@ -47,7 +47,7 @@ public abstract class AbstractFlexibleItem /** * You must implement this method to compare items identifiers. *

        Adapter needs this method to distinguish them and pick up correct items.

        - * See + * See * Writing a correct {@code equals} method to implement your own {@code equals} method. *

        Basic Java implementation: *

        
        From 730c7f5cf83a85dc2cab387c8bb7fb66aad6a5f9 Mon Sep 17 00:00:00 2001
        From: Davide Steduto 
        Date: Wed, 30 Nov 2016 23:31:30 +0100
        Subject: [PATCH 48/92] Resolves #200 - Moved getSpanCount() from
         SelectableAdapter to Utils
        
        ---
         .../flexibleadapter/SelectableAdapter.java    |  20 ---
         .../davidea/flexibleadapter/utils/Utils.java  | 137 +++++++++++++-----
         2 files changed, 100 insertions(+), 57 deletions(-)
        
        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 46d1b523..46823321 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,7 @@
         import android.os.Bundle;
         import android.support.annotation.IntDef;
         import android.support.annotation.NonNull;
        -import android.support.v7.widget.GridLayoutManager;
         import android.support.v7.widget.RecyclerView;
        -import android.support.v7.widget.StaggeredGridLayoutManager;
         import android.util.Log;
         import android.view.View;
         
        @@ -150,24 +148,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 - */ - //TODO: Deprecated? move to Utils - 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: *
          diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java index de1f4df0..3c407c6d 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java @@ -25,6 +25,7 @@ import android.os.Build.VERSION_CODES; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.OrientationHelper; import android.support.v7.widget.RecyclerView; @@ -66,6 +67,29 @@ public static boolean hasJellyBean() { return Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; } + /** + * @return the string representation of the provided {@link SelectableAdapter.Mode} + */ + @SuppressLint("SwitchIntDef") + public static String getModeName(@SelectableAdapter.Mode int mode) { + switch (mode) { + case SelectableAdapter.MODE_SINGLE: + return "MODE_SINGLE"; + case SelectableAdapter.MODE_MULTI: + return "MODE_MULTI"; + default: + return "MODE_IDLE"; + } + } + + /** + * @return the SimpleClassName of the provided object + */ + public static String getClassName(@NonNull Object o) { + return o.getClass().getSimpleName(); + } + + /** * Sets a spannable text with the accent color (if available) into the provided TextView. *

          Internally calls {@link #fetchAccentColor(Context, int)}.

          @@ -95,6 +119,23 @@ public static void highlightText(@NonNull Context context, @NonNull TextView tex } } + /** + * Resolves bug #161. Necessary when {@code theme} attribute is used in the layout. + * Used by {@code FlexibleAdapter.getStickySectionHeadersHolder()} method. + */ + public static Activity scanForActivity(Context context) { + if (context instanceof Activity) + return (Activity) context; + else if (context instanceof ContextWrapper) + return scanForActivity(((ContextWrapper) context).getBaseContext()); + + return null; + } + + /*------------------------------*/ + /* ACCENT COLOR UTILITY METHODS */ + /*------------------------------*/ + /** * Reset the internal accent color to {@link #INVALID_COLOR}, to give the possibility * to re-fetch it at runtime, since once it is fetched it cannot be changed. @@ -121,6 +162,10 @@ public static int fetchAccentColor(Context context, @ColorInt int defColor) { return colorAccent; } + /*-------------------------------*/ + /* RECYCLER-VIEW UTILITY METHODS */ + /*-------------------------------*/ + /** * Finds the layout orientation of the RecyclerView. * @@ -149,13 +194,30 @@ public static int getOrientation(RecyclerView.LayoutManager layoutManager) { } /** - * Helper method to find the adapter position of the First completely visible view [for each - * span], no matter which Layout is. + * Helper method to retrieve 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; + } + + /** + * Helper method to find the adapter position of the first completely visible view + * [for each span], no matter which Layout is. * * @param layoutManager the layout manager in use - * @return the adapter position of the first fully visible item or {@code RecyclerView.NO_POSITION} + * @return the adapter position of the first fully visible item or {@code RecyclerView.NO_POSITION} * if there aren't any visible items. - * @see #findLastCompletelyVisibleItemPosition(RecyclerView.LayoutManager) + * @see #findFirstVisibleItemPosition(RecyclerView.LayoutManager) * @since 5.0.0-b8 */ public static int findFirstCompletelyVisibleItemPosition(RecyclerView.LayoutManager layoutManager) { @@ -167,56 +229,57 @@ public static int findFirstCompletelyVisibleItemPosition(RecyclerView.LayoutMana } /** - * Helper method to find the adapter position of the Last completely visible view [for each - * span], no matter which Layout is. + * Helper method to find the adapter position of the first partially visible view + * [for each span], no matter which Layout is. * * @param layoutManager the layout manager in use - * @return the adapter position of the last fully visible item or {@code RecyclerView.NO_POSITION} + * @return the adapter position of the first partially visible item or {@code RecyclerView.NO_POSITION} * if there aren't any visible items. * @see #findFirstCompletelyVisibleItemPosition(RecyclerView.LayoutManager) - * @since 5.0.0-b8 + * @since 5.0.0-rc1 */ - public static int findLastCompletelyVisibleItemPosition(RecyclerView.LayoutManager layoutManager) { + public static int findFirstVisibleItemPosition(RecyclerView.LayoutManager layoutManager) { if (layoutManager instanceof StaggeredGridLayoutManager) { - return ((StaggeredGridLayoutManager) layoutManager).findLastCompletelyVisibleItemPositions(null)[0]; + return ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(null)[0]; } else { - return ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition(); + return ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); } } /** - * Resolves bug #161. Necessary when {@code theme} attribute is used in the layout. - * Used by {@code FlexibleAdapter.getStickySectionHeadersHolder()} method. - */ - public static Activity scanForActivity(Context context) { - if (context instanceof Activity) - return (Activity) context; - else if (context instanceof ContextWrapper) - return scanForActivity(((ContextWrapper) context).getBaseContext()); - - return null; - } - - /** - * @return the string representation of the provided {@link SelectableAdapter.Mode} + * Helper method to find the adapter position of the last completely visible view + * [for each span], no matter which Layout is. + * + * @param layoutManager the layout manager in use + * @return the adapter position of the last fully visible item or {@code RecyclerView.NO_POSITION} + * if there aren't any visible items. + * @see #findLastVisibleItemPosition(RecyclerView.LayoutManager) + * @since 5.0.0-b8 */ - @SuppressLint("SwitchIntDef") - public static String getModeName(@SelectableAdapter.Mode int mode) { - switch (mode) { - case SelectableAdapter.MODE_SINGLE: - return "MODE_SINGLE"; - case SelectableAdapter.MODE_MULTI: - return "MODE_MULTI"; - default: - return "MODE_IDLE"; + public static int findLastCompletelyVisibleItemPosition(RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof StaggeredGridLayoutManager) { + return ((StaggeredGridLayoutManager) layoutManager).findLastCompletelyVisibleItemPositions(null)[0]; + } else { + return ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition(); } } /** - * @return the SimpleClassName of the provided object + * Helper method to find the adapter position of the last partially visible view + * [for each span], no matter which Layout is. + * + * @param layoutManager the layout manager in use + * @return the adapter position of the last partially visible item or {@code RecyclerView.NO_POSITION} + * if there aren't any visible items. + * @see #findLastCompletelyVisibleItemPosition(RecyclerView.LayoutManager) + * @since 5.0.0-rc1 */ - public static String getClassName(@NonNull Object o) { - return o.getClass().getSimpleName(); + public static int findLastVisibleItemPosition(RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof StaggeredGridLayoutManager) { + return ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(null)[0]; + } else { + return ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); + } } } \ No newline at end of file From a957ee745993af9e7898c34a22541ec98861674d Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Thu, 1 Dec 2016 00:40:07 +0100 Subject: [PATCH 49/92] Decided to adopt Solution 2 for scrolling animation; Solution 1 has been removed. Fixed a couple of scrolling animation issues, now scrolling are animated properly in all situations. Scrollable Headers will now scroll animate. Scrollable Footers cannot scroll animate, when inserted the very first time --- .../flexibleadapter/ExampleAdapter.java | 3 +- .../flexibleadapter/OverallAdapter.java | 2 +- .../fragments/FragmentEndlessScrolling.java | 4 +- .../flexibleadapter/AnimatorAdapter.java | 96 ++++++------------- .../flexibleadapter/FlexibleAdapter.java | 20 ++-- 5 files changed, 45 insertions(+), 80 deletions(-) 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 bd3933ce..f060c159 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 @@ -12,6 +12,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; +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; @@ -87,7 +88,7 @@ public void showLayoutInfo() { } item.setSubtitle(mRecyclerView.getContext().getString( R.string.columns, - String.valueOf(getSpanCount(mRecyclerView.getLayoutManager()))) + String.valueOf(Utils.getSpanCount(mRecyclerView.getLayoutManager()))) ); addScrollableHeader(item); removeScrollableHeaderWithDelay(item, 4000L); 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 5a7c17f2..45527898 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 @@ -59,7 +59,7 @@ public void showLayoutInfo(boolean scrollToPosition) { } item.setSubtitle(mRecyclerView.getContext().getString( R.string.columns, - String.valueOf(getSpanCount(mRecyclerView.getLayoutManager()))) + String.valueOf(eu.davidea.flexibleadapter.utils.Utils.getSpanCount(mRecyclerView.getLayoutManager()))) ); addScrollableHeaderWithDelay(item, 500L, scrollToPosition); removeScrollableHeaderWithDelay(item, 2000L); 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 d268559f..9d2a64f0 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 @@ -66,7 +66,7 @@ public void onActivityCreated(Bundle savedInstanceState) { FlipView.resetLayoutAnimationDelay(true, 1000L); // Create New Database and Initialize RecyclerView - if (DatabaseService.getInstance().getDatabaseList().size() == 0) { + if (savedInstanceState == null) { DatabaseService.getInstance().createEndlessDatabase(0); //N. of items } initializeRecyclerView(); @@ -207,7 +207,7 @@ public void run() { Toast.LENGTH_SHORT).show(); } } - }, 5000L); + }, 4000L); } @Override 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 6044664d..79eecf68 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -124,7 +124,7 @@ private enum AnimatorEnum { * 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; } @@ -234,6 +234,7 @@ public AnimatorAdapter setAnimationOnScrolling(boolean enabled) { return this; } + //TODO: Rename to isAnimationOnScrolling() public boolean isAnimationOnScrollingEnabled() { return shouldAnimate; } @@ -343,41 +344,43 @@ private void cancelExistingAnimation(final int hashCode) { * @param holder the ViewHolder just bound * @param position the current item position */ + @SuppressWarnings("ConstantConditions") protected void animateView(final RecyclerView.ViewHolder holder, final int position) { if (mRecyclerView == null) return; - //Use always the max child count reached + // Use always the max child count reached if (mMaxChildViews < mRecyclerView.getChildCount()) { mMaxChildViews = mRecyclerView.getChildCount(); } - //Animate only during initial loading? + // 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 || (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); @@ -385,14 +388,14 @@ protected void animateView(final RecyclerView.ViewHolder holder, final int posit set.addListener(new HelperAnimatorListener(hashCode)); if (mEntryStep) { //Stop stepDelay when screen is filled - set.setStartDelay(calculateAnimationDelay2(position)); + set.setStartDelay(calculateAnimationDelay(position)); } set.start(); mAnimators.put(hashCode, set); if (DEBUG) Log.v(TAG, "animateView Scroll animation on position " + position); } - mAnimatorNotifierObserver.clearNotified(); + // Update last animated position mLastAnimatedPosition = position; } @@ -435,7 +438,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(calculateAnimationDelay(position)); } set.start(); mAnimators.put(itemView.hashCode(), set); @@ -451,63 +454,19 @@ 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()); - //Fix for high delay on the first visible item on rotation + // Fix for high delay on the first visible item on rotation if (firstVisiblePosition < 0 && position >= 0) firstVisiblePosition = position - 1; - //Last visible position is the last animated when initially loading - if (position - 1 > lastVisiblePosition) - lastVisiblePosition = position - 1; - - //Calculate visible items - 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 (position - 1 > visibleItems || //Normal Forward scrolling - (firstVisiblePosition > 1 && firstVisiblePosition <= mMaxChildViews) || //Reverse scrolling - (position > mMaxChildViews && firstVisiblePosition == -1 && mRecyclerView.getChildCount() == 0)) { //Reverse scrolling and click on FastScroller - if (DEBUG) Log.v(TAG, "Reset AnimationDelay on position " + position); - return 0L; - } - long delay = mInitialDelay; - int numColumns = getSpanCount(mRecyclerView.getLayoutManager()); - if (numColumns > 1) { - delay += mStepDelay * (position % numColumns); - } else { - delay += position * mStepDelay; - } -// if (DEBUG) Log.v(TAG, "Delay=" + delay); - return delay; - } - - /** - * 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.findFirstCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager()); - - //Fix for high delay on the first visible item on rotation -// if (firstVisiblePosition < 0 && position >= 0) -// firstVisiblePosition = position - 1; - - //Last visible position is the last animated when initially loading + // Last visible position is the last animated when initially loading if (position - 1 > lastVisiblePosition) lastVisiblePosition = position - 1; @@ -518,22 +477,22 @@ private long calculateAnimationDelay2(int position) { (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 (visibleItems <= 1) { - //When RecyclerView is initially loading no items are present - //Use InitialDelay only for the first item + // 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; } - int numColumns = getSpanCount(mRecyclerView.getLayoutManager()); + 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); } @@ -715,6 +674,7 @@ public void clearNotified() { private void markNotified() { notified = !animateFromObserver; + if (DEBUG) Log.v(TAG, "animateFromObserver=" + animateFromObserver + " notified=" + notified); } @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 c9138a71..135efa8b 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -328,7 +328,7 @@ public void onDetachedFromRecyclerView(RecyclerView recyclerView) { */ public FlexibleAdapter expandItemsAtStartUp() { int position = 0; - setAnimate(true); + setScrollAnimate(true); multiRange = true; while (position < getItemCount()) { T item = getItem(position); @@ -340,7 +340,7 @@ public FlexibleAdapter expandItemsAtStartUp() { position++; } multiRange = false; - setAnimate(false); + setScrollAnimate(false); return this; } @@ -758,6 +758,7 @@ public final List getScrollableFooters() { * @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)) { @@ -765,7 +766,9 @@ public final boolean addScrollableHeader(@NonNull T headerItem) { 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"); @@ -780,6 +783,7 @@ public final boolean addScrollableHeader(@NonNull T headerItem) { *
        • 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 @@ -1344,7 +1348,7 @@ public FlexibleAdapter setStickyHeaderContainer(@NonNull ViewGroup stickyContain * @see #setAnimationOnScrolling(boolean) * @since 5.0.0-b6 */ - //TODO: deprecation, rename to displayHeadersAtStartUp() with no parameters? + //TODO: deprecation, rename to displayHeadersAtStartUp() with animate on loading parameter? public FlexibleAdapter setDisplayHeadersAtStartUp(boolean displayHeaders) { if (!headersShown && displayHeaders) { showAllHeaders(isAnimationOnScrollingEnabled()); @@ -1890,7 +1894,7 @@ public FlexibleAdapter setEndlessScrollListener(@Nullable EndlessScrollListener public FlexibleAdapter setEndlessScrollThreshold(@IntRange(from = 1) int thresholdItems) { //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; @@ -4145,13 +4149,13 @@ private void applyAndAnimateMovedItems(List from, List newItems) { 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 + 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 - setAnimate(false);//Disable scroll animation + mItems = mTempItems; //Update mItems in the UI Thread + setScrollAnimate(false); //Disable scroll animation for (Notification notification : mNotifications) { switch (notification.operation) { case Notification.ADD: @@ -4680,7 +4684,7 @@ 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; } From 67090be102b493a4c80a595cb85d5c7ddaa49428 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Fri, 2 Dec 2016 00:03:37 +0100 Subject: [PATCH 50/92] Resolves #200 Refactored methods in AnimatorAdapter, SelectableAdapter, Utils --- .../items/ExpandableLevel1Item.java | 7 --- .../flexibleadapter/items/SimpleItem.java | 35 +++++------- .../flexibleadapter/AnimatorAdapter.java | 16 +++++- .../flexibleadapter/SelectableAdapter.java | 25 ++++++--- .../davidea/flexibleadapter/utils/Utils.java | 53 ++++++++++++++++++- 5 files changed, 95 insertions(+), 41 deletions(-) diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java index b10bc268..eed3018e 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java @@ -104,13 +104,6 @@ public void bindViewHolder(final FlexibleAdapter adapter, SimpleItem.ParentViewH //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 { diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java index 9c44e341..c8641b9c 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java @@ -80,9 +80,9 @@ public ParentViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater } @Override - @SuppressWarnings({"unchecked", "deprecation"}) + @SuppressWarnings({"unchecked"}) public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holder, int position, List payloads) { - //Subtitle + // Subtitle if (adapter.isExpandable(this)) { setSubtitle(adapter.getCurrentChildren((IExpandable) this).size() + " subItems" + (getHeader() != null ? " - " + getHeader().getId() : "") @@ -91,44 +91,33 @@ public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holde DrawableUtils.setBackground(holder.frontView, DrawableUtils.getSelectableBackgroundCompat( Color.LTGRAY, Color.WHITE, Color.LTGRAY)); - Context context = holder.itemView.getContext(); - int defColorAccent = context.getResources().getColor(R.color.colorAccent_light); if (adapter.isExpandable(this) && payloads.size() > 0) { Log.d(this.getClass().getSimpleName(), "ExpandableItem Payload " + payloads); if (adapter.hasSearchText()) { - Utils.highlightText(holder.itemView.getContext(), holder.mSubtitle, - getSubtitle(), adapter.getSearchText(), defColorAccent); + Utils.highlightText(holder.mSubtitle, getSubtitle(), adapter.getSearchText()); } else { holder.mSubtitle.setText(getSubtitle()); } - //We stop the process here, we only want to update the subtitle + // 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 + FlipView.enableLogs(true); + // 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 + // 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)); } - //In case of searchText matches with Title or with an SimpleItem's field + // 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); + Utils.highlightText(holder.mTitle, getTitle(), adapter.getSearchText()); + Utils.highlightText(holder.mSubtitle, getSubtitle(), adapter.getSearchText()); } else { holder.mTitle.setText(getTitle()); holder.mSubtitle.setText(getSubtitle()); 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 79eecf68..e89b9939 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -206,7 +206,9 @@ public AnimatorAdapter setAnimationInterpolator(@NonNull Interpolator interpolat * * @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; @@ -234,7 +236,6 @@ public AnimatorAdapter setAnimationOnScrolling(boolean enabled) { return this; } - //TODO: Rename to isAnimationOnScrolling() public boolean isAnimationOnScrollingEnabled() { return shouldAnimate; } @@ -258,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. @@ -674,7 +685,8 @@ public void clearNotified() { private void markNotified() { notified = !animateFromObserver; - if (DEBUG) Log.v(TAG, "animateFromObserver=" + animateFromObserver + " notified=" + notified); + if (DEBUG) + Log.v(TAG, "animateFromObserver=" + animateFromObserver + " notified=" + notified); } @Override 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 46823321..1ff208d8 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java @@ -166,7 +166,7 @@ public void setMode(@Mode int mode) { if (mMode == MODE_SINGLE && mode == MODE_IDLE) clearSelection(); this.mMode = mode; - mLastItemInActionMode = (mode == MODE_IDLE); + this.mLastItemInActionMode = (mode != MODE_MULTI); } /** @@ -188,26 +188,37 @@ public int getMode() { * @since 5.0.0-b1 */ public boolean isSelectAll() { + // Reset the flags with delay + resetActionModeFlags(); return mSelectAll; } /** - * @return true if user returns to {@link #MODE_IDLE} and no selection is active, false otherwise + * @return true if user returns to {@link #MODE_IDLE} or {@link #MODE_SINGLE} and no + * selection is active, false otherwise * @since 5.0.0-b1 */ public boolean isLastItemInActionMode() { + // Reset the flags with delay + resetActionModeFlags(); return mLastItemInActionMode; } /** - * Reset to false the ActionMode flags: {@code SelectAll} and {@code LastItemInActionMode}. - *

          IMPORTANT: To be called with delay in {@code holder.itemView.postDelayed()}.

          + * Resets to false the ActionMode flags: {@code SelectAll} and {@code LastItemInActionMode}. * * @since 5.0.0-b1 */ - public void resetActionModeFlags() { - this.mSelectAll = false; - this.mLastItemInActionMode = false; + private void resetActionModeFlags() { + if (mSelectAll || mLastItemInActionMode) { + mRecyclerView.postDelayed(new Runnable() { + @Override + public void run() { + mSelectAll = false; + mLastItemInActionMode = false; + } + }, 200L); + } } /** diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java index 3c407c6d..69c2aaeb 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java @@ -25,6 +25,7 @@ import android.os.Build.VERSION_CODES; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.OrientationHelper; @@ -69,6 +70,7 @@ public static boolean hasJellyBean() { /** * @return the string representation of the provided {@link SelectableAdapter.Mode} + * @since 5.0.0-rc1 */ @SuppressLint("SwitchIntDef") public static String getModeName(@SelectableAdapter.Mode int mode) { @@ -84,12 +86,12 @@ public static String getModeName(@SelectableAdapter.Mode int mode) { /** * @return the SimpleClassName of the provided object + * @since 5.0.0-rc1 */ public static String getClassName(@NonNull Object o) { return o.getClass().getSimpleName(); } - /** * Sets a spannable text with the accent color (if available) into the provided TextView. *

          Internally calls {@link #fetchAccentColor(Context, int)}.

          @@ -100,8 +102,11 @@ public static String getClassName(@NonNull Object o) { * @param constraint the text to highlight * @param defColor the default color in case accentColor is not found * @see #fetchAccentColor(Context, int) + * @deprecated Use + * {@link #highlightText(TextView, String, String, int)} OR + * {@link #highlightText(TextView, String, String)} */ - //TODO: Deprecate defColor? + @Deprecated public static void highlightText(@NonNull Context context, @NonNull TextView textView, String originalText, String constraint, @ColorInt int defColor) { if (originalText == null) originalText = ""; @@ -119,6 +124,50 @@ public static void highlightText(@NonNull Context context, @NonNull TextView tex } } + /** + * Sets a spannable text with the accent color (if available) into the provided TextView. + *

          Internally calls {@link #fetchAccentColor(Context, int)}.

          + * + * @param textView the TextView to transform + * @param originalText the original text which the transformation is applied to + * @param constraint the text to highlight + * @see #highlightText(TextView, String, String, int) + * @since 5.0.0-rc1 + */ + public static void highlightText(@NonNull TextView textView, + @Nullable String originalText, @Nullable String constraint) { + int accentColor = fetchAccentColor(textView.getContext(), 1); + highlightText(textView, originalText, constraint, accentColor); + } + + /** + * Sets a spannable text with any highlight color into the provided TextView. + * + * @param textView the TextView to transform + * @param originalText the original text which the transformation is applied to + * @param constraint the text to highlight + * @param color the highlight color + * @see #fetchAccentColor(Context, int) + * @see #highlightText(TextView, String, String) + * @since 5.0.0-rc1 + */ + public static void highlightText(@NonNull TextView textView, @Nullable String originalText, + @Nullable String constraint, @ColorInt int color) { + if (originalText == null) originalText = ""; + if (constraint == null) constraint = ""; + int i = originalText.toLowerCase(Locale.getDefault()).indexOf(constraint.toLowerCase(Locale.getDefault())); + if (i != -1) { + Spannable spanText = Spannable.Factory.getInstance().newSpannable(originalText); + spanText.setSpan(new ForegroundColorSpan(color), i, + i + constraint.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + spanText.setSpan(new StyleSpan(Typeface.BOLD), i, + i + constraint.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + textView.setText(spanText, TextView.BufferType.SPANNABLE); + } else { + textView.setText(originalText, TextView.BufferType.NORMAL); + } + } + /** * Resolves bug #161. Necessary when {@code theme} attribute is used in the layout. * Used by {@code FlexibleAdapter.getStickySectionHeadersHolder()} method. From 5f6be234a5bf92df9dc314890ed23e0653260dc0 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Fri, 2 Dec 2016 00:03:57 +0100 Subject: [PATCH 51/92] Resolves #200 Deprecated support for DiffUtil --- .../flexibleadapter/FlexibleAdapter.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) 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 135efa8b..b03f8329 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -3961,8 +3961,10 @@ public FlexibleAdapter setAnimateToLimit(int limit) { /** * @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. */ - //TODO: Deprecation: DiffUtil is slower than the internal implementation + @Deprecated public boolean isAnimateChangesWithDiffUtil() { return useDiffUtil; } @@ -3978,8 +3980,10 @@ public boolean isAnimateChangesWithDiffUtil() { * 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. */ - //TODO: Deprecation: DiffUtil is slower than the internal implementation + @Deprecated public FlexibleAdapter setAnimateChangesWithDiffUtil(boolean useDiffUtil) { this.useDiffUtil = useDiffUtil; return this; @@ -3992,14 +3996,16 @@ public FlexibleAdapter setAnimateChangesWithDiffUtil(boolean useDiffUtil) { * @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. */ - //TODO: Deprecation: DiffUtil is slower than the internal implementation + @Deprecated public FlexibleAdapter setDiffUtilCallback(DiffUtilCallback diffUtilCallback) { this.diffUtilCallback = diffUtilCallback; return this; } - //TODO: Deprecation: DiffUtil is slower than the internal implementation + @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()); @@ -5256,8 +5262,11 @@ public boolean handleMessage(Message message) { * 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. */ - //TODO: Deprecation: DiffUtil is slower than the internal implementation + @Deprecated public static class DiffUtilCallback extends DiffUtil.Callback { protected List oldItems; From cdd77108e33c09906baccef082249906bd9bdd04 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Fri, 2 Dec 2016 00:20:31 +0100 Subject: [PATCH 52/92] Resolves #227 Elevated header items and Elevated sticky header items. Resolves #228 Temporary no XML layout configuration for sticky headers. --- flexible-adapter-app/build.gradle | 2 +- .../flexibleadapter/items/SimpleItem.java | 1 - .../res/layout/fragment_recycler_view.xml | 2 +- .../helpers/StickyHeaderHelper.java | 162 +++++++++++------- .../viewholders/ContentViewHolder.java | 4 + .../main/res/layout/sticky_header_layout.xml | 3 +- 6 files changed, 108 insertions(+), 66 deletions(-) diff --git a/flexible-adapter-app/build.gradle b/flexible-adapter-app/build.gradle index 78b6323b..eb0539d4 100644 --- a/flexible-adapter-app/build.gradle +++ b/flexible-adapter-app/build.gradle @@ -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/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java index c8641b9c..e1aae91f 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java @@ -102,7 +102,6 @@ public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holde // We stop the process here, we only want to update the subtitle } else { - FlipView.enableLogs(true); // DemoApp: INNER ANIMATION EXAMPLE! ImageView - Handle Flip Animation on // Select ALL and Deselect ALL if (adapter.isSelectAll() || adapter.isLastItemInActionMode()) { 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 9c5f19a7..31f8921b 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 @@ -28,7 +28,7 @@ - + diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index 68e71005..910b3e5b 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -16,18 +16,23 @@ package eu.davidea.flexibleadapter.helpers; import android.animation.Animator; +import android.graphics.Color; +import android.support.v4.view.ViewCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.OrientationHelper; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.OnScrollListener; import android.support.v7.widget.StaggeredGridLayoutManager; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; +import android.widget.FrameLayout; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.FlexibleAdapter.OnStickyHeaderChangeListener; +import eu.davidea.flexibleadapter.R; import eu.davidea.flexibleadapter.items.IHeader; import eu.davidea.flexibleadapter.utils.Utils; import eu.davidea.viewholders.FlexibleViewHolder; @@ -59,6 +64,7 @@ public StickyHeaderHelper(FlexibleAdapter adapter, @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + displayWithAnimation = mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE; updateOrClearHeader(false); } @@ -81,24 +87,46 @@ public void detachFromRecyclerView() { if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout detached"); } +// private FrameLayout createContainer(int width, int height) { +// FrameLayout frameLayout = new FrameLayout(mRecyclerView.getContext()); +// frameLayout.setLayoutParams(new ViewGroup.LayoutParams(width, height)); +// return frameLayout; +// } + + private static ViewGroup getParent(View view) { + return (ViewGroup)view.getParent(); + } + private void initStickyHeadersHolder() { - //Initialize Holder Layout and show sticky header if exists already - mStickyHolderLayout = mAdapter.getStickySectionHeadersHolder(); - if (mStickyHolderLayout != null) { - if (mStickyHolderLayout.getLayoutParams() == null) { - throw new IllegalStateException("The ViewGroup provided, doesn't have LayoutParams correctly set, please initialize the ViewGroup accordingly"); - } - mStickyHolderLayout.setClipToPadding(false); - mStickyHolderLayout.setAlpha(0); - updateOrClearHeader(false); - mStickyHolderLayout.animate().alpha(1).start(); - if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout initialized"); - } else { - throw new IllegalStateException("ViewGroup for Sticky Headers unspecified! You must either include @layout/sticky_header_layout OR set a custom StickyHeaderContainer"); - } + // Create stickyContainer for shadow elevation + FrameLayout stickyContainer = new FrameLayout(mRecyclerView.getContext()); + stickyContainer.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + ViewGroup oldParentLayout = getParent(mRecyclerView); + oldParentLayout.addView(stickyContainer); + + // Initialize Holder Layout + mStickyHolderLayout = (ViewGroup) LayoutInflater.from(mRecyclerView.getContext()).inflate(R.layout.sticky_header_layout, stickyContainer); + +// mStickyHolderLayout = mAdapter.getStickySectionHeadersHolder(); +// if (mStickyHolderLayout != null) { +// if (mStickyHolderLayout.getLayoutParams() == null) { +// throw new IllegalStateException("The ViewGroup provided, doesn't have LayoutParams correctly set, please initialize the ViewGroup accordingly"); +// } + + // Show sticky header if exists already + updateOrClearHeader(false); + mStickyHolderLayout.setAlpha(0); + mStickyHolderLayout.animate().alpha(1).start(); + if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout initialized"); + +// } else { +// throw new IllegalStateException("ViewGroup for Sticky Headers unspecified! You must either include @layout/sticky_header_layout OR set a custom StickyHeaderContainer"); +// } } - public boolean hasStickyHeaderTranslated(int position) { + private boolean hasStickyHeaderTranslated(int position) { RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(position); return vh != null && (vh.itemView.getX() < 0 || vh.itemView.getY() < 0); } @@ -123,14 +151,16 @@ public void updateOrClearHeader(boolean updateHeaderContent) { } private void updateHeader(int headerPosition, boolean updateHeaderContent) { - // Animate if headers were hidden - if (displayWithAnimation) { - displayWithAnimation = false; - mStickyHolderLayout.setAlpha(0); - mStickyHolderLayout.animate().alpha(1).start(); - } // Check if there is a new header to be sticky if (mHeaderPosition != headerPosition) { + // Animate if headers were hidden, but don't if configuration changed (rotation) + if (displayWithAnimation && mHeaderPosition == RecyclerView.NO_POSITION) { + displayWithAnimation = false; + mStickyHolderLayout.setAlpha(0); + mStickyHolderLayout.animate().alpha(1).start(); + } else { + mStickyHolderLayout.setAlpha(1); + } mHeaderPosition = headerPosition; FlexibleViewHolder holder = getHeaderViewHolder(headerPosition); if (FlexibleAdapter.DEBUG) @@ -144,11 +174,12 @@ private void updateHeader(int headerPosition, boolean updateHeaderContent) { } private void translateHeader() { - if (mStickyHeaderViewHolder == null) return; - int headerOffsetX = 0, headerOffsetY = 0; + float elevation = 21f; //Default elevation 6dp = 3.5 for each dp + if (Utils.hasLollipop()) + elevation = mStickyHeaderViewHolder.getContentView().getElevation(); - //Search for the position where the next header item is found and translate the new offset + // Search for the position where the next header item is found and translate the new offset for (int i = 0; i < mRecyclerView.getChildCount(); i++) { final View nextChild = mRecyclerView.getChildAt(i); if (nextChild != null) { @@ -158,27 +189,33 @@ private void translateHeader() { if (Utils.getOrientation(mRecyclerView.getLayoutManager()) == OrientationHelper.HORIZONTAL) { if (nextChild.getLeft() > 0) { int headerWidth = mStickyHolderLayout.getMeasuredWidth(); - headerOffsetX = Math.min(nextChild.getLeft() - headerWidth, 0); + int nextHeaderOffsetX = nextChild.getLeft() - headerWidth; + headerOffsetX = Math.min(nextHeaderOffsetX, 0); // TODO: AlphaListener = Math.abs((float)nextChild.getLeft()) / headerWidth); + // Early remove the elevation/shadow to match with the next view + if (nextHeaderOffsetX < 5) elevation = 0f; if (headerOffsetX < 0) break; } } else { if (nextChild.getTop() > 0) { int headerHeight = mStickyHolderLayout.getMeasuredHeight(); - headerOffsetY = Math.min(nextChild.getTop() - headerHeight, 0); + int nextHeaderOffsetY = nextChild.getTop() - headerHeight; + headerOffsetY = Math.min(nextHeaderOffsetY, 0); // TODO: AlphaListener = Math.abs((float)nextChild.getTop()) / headerHeight); + // Early remove the elevation/shadow to match with the next view + if (nextHeaderOffsetY < 5) elevation = 0f; if (headerOffsetY < 0) break; } } } } } - //Fix to remove unnecessary shadow - //ViewCompat.setElevation(mStickyHeaderViewHolder.getContentView(), 0f); - //Apply translation + // Apply the calculated elevation + ViewCompat.setElevation(mStickyHolderLayout, elevation); + // Apply translation mStickyHolderLayout.setTranslationX(headerOffsetX); mStickyHolderLayout.setTranslationY(headerOffsetY); - //Log.v(TAG, "TranslationX=" + headerOffsetX + " TranslationY=" + headerOffsetY); +// Log.v(TAG, "TranslationX=" + headerOffsetX + " TranslationY=" + headerOffsetY); } private void swapHeader(FlexibleViewHolder newHeader) { @@ -195,26 +232,26 @@ private void swapHeader(FlexibleViewHolder newHeader) { private void ensureHeaderParent() { final View view = mStickyHeaderViewHolder.getContentView(); - //#121 - Make sure the measured height (width for horizontal layout) is kept if + // #121 - Make sure the measured height (width for horizontal layout) is kept if // WRAP_CONTENT has been set for the Header View mStickyHeaderViewHolder.itemView.getLayoutParams().width = view.getMeasuredWidth(); mStickyHeaderViewHolder.itemView.getLayoutParams().height = view.getMeasuredHeight(); - //#139 - Copy xml params instead of Measured params + // #139 - Copy xml params instead of Measured params ViewGroup.LayoutParams params = mStickyHolderLayout.getLayoutParams(); params.width = view.getLayoutParams().width; params.height = view.getLayoutParams().height; removeViewFromParent(view); - mStickyHolderLayout.setClipToPadding(false); mStickyHolderLayout.addView(view); + //TODO: set custom background for transparency (elevation will be lost!) + mStickyHolderLayout.setBackgroundColor(Color.WHITE);// Needed to elevate the view } private void resetHeader(FlexibleViewHolder header) { final View view = header.getContentView(); removeViewFromParent(view); - //Reset transformation on removed header + // Reset transformation on removed header view.setTranslationX(0); view.setTranslationY(0); - mStickyHeaderViewHolder.itemView.setVisibility(View.VISIBLE); if (!header.itemView.equals(view)) ((ViewGroup) header.itemView).addView(view); header.setIsRecyclable(true); @@ -224,7 +261,8 @@ private void clearHeader() { if (mStickyHeaderViewHolder != null) { if (FlexibleAdapter.DEBUG) Log.d(TAG, "clearHeader"); resetHeader(mStickyHeaderViewHolder); - mStickyHolderLayout.setAlpha(1); + mStickyHolderLayout.setBackgroundColor(Color.TRANSPARENT); + mStickyHolderLayout.animate().setListener(null); mStickyHeaderViewHolder = null; mHeaderPosition = RecyclerView.NO_POSITION; onStickyHeaderChange(mHeaderPosition); @@ -232,27 +270,29 @@ private void clearHeader() { } public void clearHeaderWithAnimation() { - mStickyHolderLayout.animate().setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } + if (mStickyHeaderViewHolder != null && mHeaderPosition != RecyclerView.NO_POSITION) { + mStickyHolderLayout.animate().setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mHeaderPosition = RecyclerView.NO_POSITION; + } - @Override - public void onAnimationEnd(Animator animation) { - mStickyHolderLayout.animate().setListener(null); - displayWithAnimation = true; - clearHeader(); - } + @Override + public void onAnimationEnd(Animator animation) { + mStickyHolderLayout.setAlpha(0); + clearHeader(); + } - @Override - public void onAnimationCancel(Animator animation) { - } + @Override + public void onAnimationCancel(Animator animation) { + } - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - mStickyHolderLayout.animate().alpha(0).start(); + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + mStickyHolderLayout.animate().alpha(0).start(); + } } private static void removeViewFromParent(final View view) { @@ -265,7 +305,7 @@ private static void removeViewFromParent(final View view) { @SuppressWarnings("unchecked") private int getHeaderPosition(int adapterPosHere) { if (adapterPosHere == RecyclerView.NO_POSITION) { - //Fix to display correct sticky header (especially after the searchText is cleared out) + // Fix to display correct sticky header (especially after the searchText is cleared out) if (mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) { adapterPosHere = ((StaggeredGridLayoutManager) mRecyclerView.getLayoutManager()).findFirstVisibleItemPositions(null)[0]; } else { @@ -276,7 +316,7 @@ private int getHeaderPosition(int adapterPosHere) { } } IHeader header = mAdapter.getSectionHeader(adapterPosHere); - //Header cannot be sticky if it's also an Expandable in collapsed status, RV will raise an exception + // Header cannot be sticky if it's also an Expandable in collapsed status, RV will raise an exception if (header == null || mAdapter.isExpandable(header) && !mAdapter.isExpanded(header)) { return RecyclerView.NO_POSITION; } @@ -292,17 +332,17 @@ private int getHeaderPosition(int adapterPosHere) { */ @SuppressWarnings("unchecked") private FlexibleViewHolder getHeaderViewHolder(int position) { - //Find existing ViewHolder + // Find existing ViewHolder FlexibleViewHolder holder = (FlexibleViewHolder) mRecyclerView.findViewHolderForAdapterPosition(position); if (holder == null) { - //Create and binds a new ViewHolder + // Create and binds a new ViewHolder holder = (FlexibleViewHolder) mAdapter.createViewHolder(mRecyclerView, mAdapter.getItemViewType(position)); mAdapter.bindViewHolder(holder, position); - //Restore the Adapter position + // Restore the Adapter position holder.setBackupPosition(position); - //Calculate width and height + // Calculate width and height int widthSpec; int heightSpec; if (Utils.getOrientation(mRecyclerView.getLayoutManager()) == OrientationHelper.VERTICAL) { @@ -313,7 +353,7 @@ private FlexibleViewHolder getHeaderViewHolder(int position) { heightSpec = View.MeasureSpec.makeMeasureSpec(mRecyclerView.getHeight(), View.MeasureSpec.EXACTLY); } - //Measure and Layout the stickyView + // Measure and Layout the stickyView final View headerView = holder.getContentView(); int childWidth = ViewGroup.getChildMeasureSpec(widthSpec, mRecyclerView.getPaddingLeft() + mRecyclerView.getPaddingRight(), diff --git a/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java b/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java index e8488f25..8a318e2e 100644 --- a/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java +++ b/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java @@ -15,6 +15,8 @@ */ package eu.davidea.viewholders; +import android.graphics.Color; +import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.FrameLayout; @@ -49,6 +51,8 @@ abstract class ContentViewHolder extends RecyclerView.ViewHolder { itemView.setLayoutParams(adapter.getRecyclerView().getLayoutManager() .generateLayoutParams(view.getLayoutParams())); ((FrameLayout) itemView).addView(view); //Add View after setLayoutParams + itemView.setBackgroundColor(Color.WHITE); + ViewCompat.setElevation(itemView, ViewCompat.getElevation(view)); contentView = view; } } diff --git a/flexible-adapter/src/main/res/layout/sticky_header_layout.xml b/flexible-adapter/src/main/res/layout/sticky_header_layout.xml index f446d759..0412752e 100644 --- a/flexible-adapter/src/main/res/layout/sticky_header_layout.xml +++ b/flexible-adapter/src/main/res/layout/sticky_header_layout.xml @@ -2,5 +2,4 @@ \ No newline at end of file + android:layout_height="wrap_content"/> \ No newline at end of file From 07a9fe24308096a74c25eb53399c993d75aacbc9 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Fri, 2 Dec 2016 00:24:14 +0100 Subject: [PATCH 53/92] Runtime background, changing state has short enter-exit fade animation --- .../flexibleadapter/utils/DrawableUtils.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java index fcd40f01..a861f030 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java @@ -15,7 +15,6 @@ */ package eu.davidea.flexibleadapter.utils; -import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.ColorDrawable; @@ -46,8 +45,8 @@ public final class DrawableUtils { * @param drawable drawable object * @since 5.0.0-b7 */ + //TODO: Rename to setBackgroundCompat @SuppressWarnings("deprecation") - @SuppressLint("NewApi") public static void setBackground(View view, Drawable drawable) { if (Utils.hasJellyBean()) { view.setBackground(drawable); @@ -63,6 +62,7 @@ public static void setBackground(View view, Drawable drawable) { * @param drawableRes drawable resource id * @since 5.0.0-b7 */ + //TODO: Rename to setBackgroundCompat public static void setBackground(View view, @DrawableRes int drawableRes) { setBackground(view, getDrawableCompat(view.getContext(), drawableRes)); } @@ -89,12 +89,14 @@ public static Drawable getDrawableCompat(Context context, @DrawableRes int drawa } /** - * Helper to get the system default Selectable Background. + * Helper to get the default Selectable Background. + * Returns the resourceId of the {@code R.attr.selectableItemBackground} attribute in your style. * * @param context the context * @return Default selectable background resId * @since 5.0.0-b7 */ + //TODO: Rename to getDefaultSelectableBackground ? public static int getSelectableBackground(Context context) { TypedValue outValue = new TypedValue(); //it is important here to not use the android.R because this wouldn't add the latest drawable @@ -104,6 +106,7 @@ public static int getSelectableBackground(Context context) { /** * Helper to get the system default Color Control Highlight. + * Returns the resourceId of the {@code R.attr.colorControlHighlight} attribute in your style. * * @param context the context * @return Default Color Control Highlight resId @@ -126,10 +129,12 @@ public static int getColorControlHighlight(Context context) { * StateListDrawable otherwise * @since 5.0.0-b7 */ + //TODO: Deprecate? change the method name and put the rippleColor to the end public static Drawable getSelectableBackgroundCompat(@ColorInt int rippleColor, @ColorInt int normalColor, @ColorInt int pressedColor) { if (Utils.hasLollipop()) { + //TODO: create a color state list for ripple based on state return new RippleDrawable(ColorStateList.valueOf(rippleColor), getStateListDrawable(normalColor, pressedColor, true), getRippleMask(normalColor)); @@ -156,12 +161,10 @@ private static StateListDrawable getStateListDrawable(@ColorInt int normalColor, states.addState(new int[]{android.R.attr.state_pressed}, getColorDrawable(pressedColor)); states.addState(new int[]{android.R.attr.state_activated}, getColorDrawable(pressedColor)); states.addState(new int[]{}, getColorDrawable(normalColor)); - //if possible we enable animating across states - if (!withRipple) { - int duration = 200; //android.R.integer.config_shortAnimTime - states.setEnterFadeDuration(duration); - states.setExitFadeDuration(duration); - } + //Animating across states + int duration = 200; //android.R.integer.config_shortAnimTime + states.setEnterFadeDuration(duration); + states.setExitFadeDuration(duration); return states; } From 21fc8740d9a4df8e8fc8089a7c3e101c0468bae4 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Fri, 2 Dec 2016 17:06:27 +0100 Subject: [PATCH 54/92] Resolves #200 Started to refactor functions in FlexibleAdapter --- .../flexibleadapter/ExampleAdapter.java | 2 +- .../fragments/FragmentHeadersSections.java | 9 +++ .../fragments/FragmentInstagramHeaders.java | 2 +- .../items/ScrollableSubItem.java | 2 +- .../flexibleadapter/items/SimpleItem.java | 2 +- .../flexibleadapter/items/StaggeredItem.java | 6 +- .../flexibleadapter/FlexibleAdapter.java | 77 +++++++++++++++---- .../helpers/StickyHeaderHelper.java | 51 ++++++------ .../davidea/flexibleadapter/utils/Utils.java | 3 +- 9 files changed, 105 insertions(+), 49 deletions(-) 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 f060c159..6ce8232a 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 @@ -152,7 +152,7 @@ public void addScrollableExpandableAsFooter() { * {@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 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 fe40a57d..3c6b5cf4 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 @@ -116,6 +116,15 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mAdapter.addUserLearnedSelection(savedInstanceState == null); mAdapter.showLayoutInfo(); mAdapter.addScrollableFooter(); + +// mRecyclerView.postDelayed(new Runnable() { +// @Override +// public void run() { +// IHeader newHeader = DatabaseService.newHeader(555); +// SimpleItem iSectionable = DatabaseService.newSimpleItem(999, newHeader); +// mAdapter.addItem(0, iSectionable); +// } +// }, 5000L); } @Override 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 1a164caa..cb41d1c4 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 @@ -69,7 +69,7 @@ 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()) + mAdapter.addListener(getActivity()) //Experimenting NEW features (v5.0.0) .setAnimationOnScrolling(true) .setAnimationOnReverseScrolling(true); 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 index 47ab65e5..71f77315 100644 --- 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 @@ -37,7 +37,7 @@ public ChildViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater @Override @SuppressWarnings("unchecked") public void bindViewHolder(FlexibleAdapter adapter, ChildViewHolder holder, int position, List payloads) { - String title = "Scrollable SubItem " + adapter.getRelativePositionOf(this); + String title = "Scrollable SubItem " + adapter.getSubPositionOf(this); holder.mTitle.setText(title); } diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java index e1aae91f..f1c3f428 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java @@ -89,7 +89,7 @@ public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holde + (getUpdates() > 0 ? " - u" + getUpdates() : "")); } - DrawableUtils.setBackground(holder.frontView, DrawableUtils.getSelectableBackgroundCompat( + DrawableUtils.setBackground(holder.itemView, DrawableUtils.getSelectableBackgroundCompat( Color.LTGRAY, Color.WHITE, Color.LTGRAY)); if (adapter.isExpandable(this) && payloads.size() > 0) { diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java index 46de2b25..7671654a 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java @@ -123,10 +123,8 @@ public void bindViewHolder(final FlexibleAdapter adapter, final ViewHolder holde //Item Status holder.statusTextView.setText(status.getResId()); - DrawableUtils.setBackground(holder.itemView, - DrawableUtils.getSelectableBackgroundCompat( - Color.WHITE, status.getColor(), - Utils.getColorAccent(holder.itemView.getContext()))); + DrawableUtils.setBackground(holder.itemView, DrawableUtils.getSelectableBackgroundCompat( + Color.WHITE, status.getColor(), Utils.getColorAccent(holder.itemView.getContext()))); //Blink after moving the item for (Object payload : payloads) { 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 b03f8329..21ee939d 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -209,7 +209,7 @@ public FlexibleAdapter(@Nullable List items) { *
        * @see #FlexibleAdapter(List) * @see #FlexibleAdapter(List, Object, boolean) - * @see #initializeListeners(Object) + * @see #addListener(Object) * @since 5.0.0-b1 */ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners) { @@ -227,7 +227,7 @@ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners) { * @param stableIds set {@code true} if item implements {@code hashcode()} and have stable ids. * @see #FlexibleAdapter(List) * @see #FlexibleAdapter(List, Object) - * @see #initializeListeners(Object) + * @see #addListener(Object) * @since 5.0.0-b8 */ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners, boolean stableIds) { @@ -240,12 +240,25 @@ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners, bool mOrphanHeaders = new ArrayList<>(); //Create listeners instances - initializeListeners(listeners); + addListener(listeners); //Get notified when items are inserted or removed (it adjusts selected positions) registerAdapterDataObserver(new AdapterDataObserver()); } + /** + * 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 + * @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.

        @@ -254,11 +267,10 @@ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners, bool * @return this Adapter, so the call can be chained * @since 5.0.0-b6 */ - //TODO: Rename to addListener? @CallSuper - public FlexibleAdapter initializeListeners(@Nullable Object listener) { + public FlexibleAdapter addListener(@Nullable Object listener) { if (DEBUG && listener != null) { - Log.i(TAG, "Initialize Class " + getClassName(listener) + " as:"); + Log.i(TAG, "Add listener class " + getClassName(listener) + " as:"); } if (listener instanceof OnItemClickListener) { if (DEBUG) Log.i(TAG, "- OnItemClickListener"); @@ -297,6 +309,7 @@ public FlexibleAdapter initializeListeners(@Nullable Object listener) { @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); + //TODO is this necessary? if (mStickyHeaderHelper != null && headersShown) { mStickyHeaderHelper.attachToRecyclerView(mRecyclerView); } @@ -310,6 +323,7 @@ public void onAttachedToRecyclerView(RecyclerView recyclerView) { */ @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { + //TODO is this necessary? if (mStickyHeaderHelper != null) { mStickyHeaderHelper.detachFromRecyclerView(); mStickyHeaderHelper = null; @@ -1246,12 +1260,12 @@ public boolean areHeadersSticky() { * Enables the sticky header functionality. *

        Headers can be sticky only if they are shown.

        * Note: - *
        - You must read {@link #getStickySectionHeadersHolder()}. + *
        - 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 */ //TODO: deprecation use setStickyHeaders(true)? @@ -1305,9 +1319,9 @@ public void run() { * * @return ViewGroup layout that will hold the sticky header ItemViews * @since 5.0.0-b6 + * @deprecated Use {@link #getStickyHeaderContainer()} */ - //TODO: Review the comment for the new stickyHeaders helper - //TODO: Rename the method to getStickyHeaderContainer + @Deprecated public ViewGroup getStickySectionHeadersHolder() { if (mStickyContainer == null) { mStickyContainer = (ViewGroup) Utils @@ -1318,13 +1332,29 @@ public ViewGroup getStickySectionHeadersHolder() { } /** - * Sets a custom {@link ViewGroup} for Sticky Headers when the default can't be used. - *

        Useful in conjunction with ViewPager.

        + * 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 + */ + //TODO: Integrate the usage of the custom container in the new StickyHeaderHelper + //TODO: Review the comment for the new StickyHeaderHelper: layout is not needed anymore + public final 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 */ public FlexibleAdapter setStickyHeaderContainer(@NonNull ViewGroup stickyContainer) { + if (mStickyHeaderHelper != null) { + Log.w(TAG, "StickyHeaderHelper has been already initialized! Call this method before enabling StickyHeaders"); + } if (DEBUG && stickyContainer != null) Log.i(TAG, "Set stickyHeaderContainer=" + getClassName(stickyContainer)); this.mStickyContainer = stickyContainer; @@ -2168,7 +2198,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) { @@ -2195,7 +2225,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) { @@ -2211,12 +2241,27 @@ public int getExpandablePositionOf(@NonNull T child) { * @see #getExpandableOf(IFlexible) * @see #getExpandablePositionOf(IFlexible) * @since 5.0.0-b1 + * @deprecated Use {@link #getSubPositionOf(IFlexible)} */ - //TODO: Rename to getSubPositionOf + @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); + } + /** * Provides the full sub list where the child currently lays. * @@ -2224,7 +2269,7 @@ public int getRelativePositionOf(@NonNull T child) { * @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 #getRelativePositionOf(IFlexible) + * @see #getSubPositionOf(IFlexible) * @see #getExpandedItems() * @since 5.0.0-b1 */ diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index 910b3e5b..c273ddd2 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -43,7 +43,7 @@ * * @since 25/03/2016 Created */ -public class StickyHeaderHelper extends OnScrollListener { +public final class StickyHeaderHelper extends OnScrollListener { private static final String TAG = StickyHeaderHelper.class.getSimpleName(); @@ -73,6 +73,10 @@ public void attachToRecyclerView(RecyclerView parent) { mRecyclerView.removeOnScrollListener(this); clearHeader(); } + if (parent == null) { + Log.e(TAG, "Adapter is not attached to RecyclerView. Enable sticky headers after setting adapter to RecyclerView."); + //TODO: evaluate throwing IllegalStateException + } mRecyclerView = parent; if (mRecyclerView != null) { mRecyclerView.addOnScrollListener(this); @@ -87,6 +91,10 @@ public void detachFromRecyclerView() { if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout detached"); } +// public final void updateStickyHolderLayout(ViewGroup mStickyHolderLayout) { +// this.mStickyHolderLayout = mStickyHolderLayout; +// } + // private FrameLayout createContainer(int width, int height) { // FrameLayout frameLayout = new FrameLayout(mRecyclerView.getContext()); // frameLayout.setLayoutParams(new ViewGroup.LayoutParams(width, height)); @@ -98,32 +106,25 @@ private static ViewGroup getParent(View view) { } private void initStickyHeadersHolder() { - // Create stickyContainer for shadow elevation - FrameLayout stickyContainer = new FrameLayout(mRecyclerView.getContext()); - stickyContainer.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - ViewGroup oldParentLayout = getParent(mRecyclerView); - oldParentLayout.addView(stickyContainer); - - // Initialize Holder Layout - mStickyHolderLayout = (ViewGroup) LayoutInflater.from(mRecyclerView.getContext()).inflate(R.layout.sticky_header_layout, stickyContainer); - -// mStickyHolderLayout = mAdapter.getStickySectionHeadersHolder(); -// if (mStickyHolderLayout != null) { -// if (mStickyHolderLayout.getLayoutParams() == null) { -// throw new IllegalStateException("The ViewGroup provided, doesn't have LayoutParams correctly set, please initialize the ViewGroup accordingly"); -// } - + mStickyHolderLayout = mAdapter.getStickyHeaderContainer(); + if (mStickyHolderLayout == null) { + // Create stickyContainer for shadow elevation + FrameLayout stickyContainer = new FrameLayout(mRecyclerView.getContext()); + stickyContainer.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + ViewGroup oldParentLayout = getParent(mRecyclerView); + oldParentLayout.addView(stickyContainer); + // Initialize Holder Layout + mStickyHolderLayout = (ViewGroup) LayoutInflater.from(mRecyclerView.getContext()).inflate(R.layout.sticky_header_layout, stickyContainer); + if (FlexibleAdapter.DEBUG) Log.i(TAG, "Default StickyHolderLayout initialized"); + } else if (FlexibleAdapter.DEBUG) { + Log.i(TAG, "User StickyHolderLayout initialized"); + } // Show sticky header if exists already updateOrClearHeader(false); mStickyHolderLayout.setAlpha(0); mStickyHolderLayout.animate().alpha(1).start(); - if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout initialized"); - -// } else { -// throw new IllegalStateException("ViewGroup for Sticky Headers unspecified! You must either include @layout/sticky_header_layout OR set a custom StickyHeaderContainer"); -// } } private boolean hasStickyHeaderTranslated(int position) { @@ -143,7 +144,7 @@ public void updateOrClearHeader(boolean updateHeaderContent) { return; } int firstHeaderPosition = getHeaderPosition(RecyclerView.NO_POSITION); - if (firstHeaderPosition >= 0 && firstHeaderPosition < mAdapter.getItemCount()) { + if (firstHeaderPosition >= 0) { updateHeader(firstHeaderPosition, updateHeaderContent); } else { clearHeader(); @@ -153,8 +154,10 @@ public void updateOrClearHeader(boolean updateHeaderContent) { private void updateHeader(int headerPosition, boolean updateHeaderContent) { // Check if there is a new header to be sticky if (mHeaderPosition != headerPosition) { +// int firstVisibleItemPosition = Utils.findFirstVisibleItemPosition(mRecyclerView.getLayoutManager()); // Animate if headers were hidden, but don't if configuration changed (rotation) if (displayWithAnimation && mHeaderPosition == RecyclerView.NO_POSITION) { +// headerPosition != firstVisibleItemPosition) { displayWithAnimation = false; mStickyHolderLayout.setAlpha(0); mStickyHolderLayout.animate().alpha(1).start(); diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java index 69c2aaeb..ed90decf 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java @@ -170,8 +170,9 @@ public static void highlightText(@NonNull TextView textView, @Nullable String or /** * Resolves bug #161. Necessary when {@code theme} attribute is used in the layout. - * Used by {@code FlexibleAdapter.getStickySectionHeadersHolder()} method. + * Used by {@code FlexibleAdapter.getStickyHeaderContainer()} method. */ + //TODO: review comment public static Activity scanForActivity(Context context) { if (context instanceof Activity) return (Activity) context; From f72d08f3b80884f2a767d43645d1380abcfd071e Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 3 Dec 2016 19:43:04 +0100 Subject: [PATCH 55/92] Added version log at startup and Info on animation when changing LayoutManager --- .../eu/davidea/samples/flexibleadapter/ExampleAdapter.java | 5 ++++- .../java/eu/davidea/flexibleadapter/AnimatorAdapter.java | 5 ++++- .../java/eu/davidea/flexibleadapter/FlexibleAdapter.java | 6 +++--- .../java/eu/davidea/flexibleadapter/SelectableAdapter.java | 1 + 4 files changed, 12 insertions(+), 5 deletions(-) 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 6ce8232a..e212ec8b 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 @@ -90,7 +90,10 @@ public void showLayoutInfo() { R.string.columns, String.valueOf(Utils.getSpanCount(mRecyclerView.getLayoutManager()))) ); - addScrollableHeader(item); + // 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, 0L, true); removeScrollableHeaderWithDelay(item, 4000L); } } 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 e89b9939..cac08517 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -107,8 +107,8 @@ private enum AnimatorEnum { */ AnimatorAdapter(boolean stableIds) { super(); - if (stableIds && DEBUG) Log.i("FlexibleAdapter", "initialize with 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(); @@ -351,6 +351,9 @@ private void cancelExistingAnimation(final int hashCode) { /** * 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 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 21ee939d..1f08f079 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -270,7 +270,7 @@ public FlexibleAdapter initializeListeners(@Nullable Object listener) { @CallSuper public FlexibleAdapter addListener(@Nullable Object listener) { if (DEBUG && listener != null) { - Log.i(TAG, "Add listener class " + getClassName(listener) + " as:"); + Log.i(TAG, "Adding listener class " + getClassName(listener) + " as:"); } if (listener instanceof OnItemClickListener) { if (DEBUG) Log.i(TAG, "- OnItemClickListener"); @@ -4252,8 +4252,8 @@ private void initializeItemTouchHelper() { 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) { - if (DEBUG) Log.i(TAG, "Initialize default ItemTouchHelperCallback"); mItemTouchHelperCallback = new ItemTouchHelperCallback(this); + if (DEBUG) Log.i(TAG, "Initialized default ItemTouchHelperCallback"); } mItemTouchHelper = new ItemTouchHelper(mItemTouchHelperCallback); mItemTouchHelper.attachToRecyclerView(mRecyclerView); @@ -4295,10 +4295,10 @@ public final ItemTouchHelperCallback getItemTouchHelperCallback() { * @since 5.0.0-rc1 */ public final FlexibleAdapter setItemTouchHelperCallback(ItemTouchHelperCallback itemTouchHelperCallback) { - if (DEBUG) Log.i(TAG, "Initialize custom ItemTouchHelperCallback"); mItemTouchHelperCallback = itemTouchHelperCallback; mItemTouchHelper = null; initializeItemTouchHelper(); + if (DEBUG) Log.i(TAG, "Initialized custom ItemTouchHelperCallback"); return this; } 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 1ff208d8..68a0f0fe 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java @@ -95,6 +95,7 @@ public abstract class SelectableAdapter extends RecyclerView.Adapter * @since 1.0.0 */ public SelectableAdapter() { + Log.i("FlexibleAdapter", "Running version " + BuildConfig.VERSION_NAME); mSelectedPositions = new TreeSet<>(); mMode = MODE_IDLE; } From 684e3a1771301cb1f8b1adf5268808c2b9445de1 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sat, 3 Dec 2016 19:44:20 +0100 Subject: [PATCH 56/92] Added more gradle tasks --- maven-install.gradle | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/maven-install.gradle b/maven-install.gradle index be81a1c9..c5d463db 100644 --- a/maven-install.gradle +++ b/maven-install.gradle @@ -2,6 +2,30 @@ apply plugin: 'com.github.dcendents.android-maven' group = publishedGroupId // Maven Group ID for the artifact +task clean(type: Delete) { + delete rootProject.buildDir +} + +task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' +} + +task javadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.getDestinationDir() +} + +artifacts { + archives javadocJar + archives sourcesJar +} + install { repositories.mavenInstaller { // This generates POM.xml with proper parameters From 697e8b0d55954a53d9bb16a8a89ef1324f861eef Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 4 Dec 2016 15:54:24 +0100 Subject: [PATCH 57/92] Partially resolved #244, header item doesn't swap if it matches with the first layout position --- .../helpers/StickyHeaderHelper.java | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index c273ddd2..ad89d29b 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -57,9 +57,11 @@ public final class StickyHeaderHelper extends OnScrollListener { public StickyHeaderHelper(FlexibleAdapter adapter, - OnStickyHeaderChangeListener stickyHeaderChangeListener) { + OnStickyHeaderChangeListener stickyHeaderChangeListener, + ViewGroup stickyHolderLayout) { mAdapter = adapter; mStickyHeaderChangeListener = stickyHeaderChangeListener; + mStickyHolderLayout = stickyHolderLayout; } @Override @@ -74,14 +76,11 @@ public void attachToRecyclerView(RecyclerView parent) { clearHeader(); } if (parent == null) { - Log.e(TAG, "Adapter is not attached to RecyclerView. Enable sticky headers after setting adapter to RecyclerView."); - //TODO: evaluate throwing IllegalStateException + throw new IllegalStateException("Adapter is not attached to RecyclerView. Enable sticky headers after setting adapter to RecyclerView."); } mRecyclerView = parent; - if (mRecyclerView != null) { - mRecyclerView.addOnScrollListener(this); - initStickyHeadersHolder(); - } + mRecyclerView.addOnScrollListener(this); + initStickyHeadersHolder(); } public void detachFromRecyclerView() { @@ -91,10 +90,6 @@ public void detachFromRecyclerView() { if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout detached"); } -// public final void updateStickyHolderLayout(ViewGroup mStickyHolderLayout) { -// this.mStickyHolderLayout = mStickyHolderLayout; -// } - // private FrameLayout createContainer(int width, int height) { // FrameLayout frameLayout = new FrameLayout(mRecyclerView.getContext()); // frameLayout.setLayoutParams(new ViewGroup.LayoutParams(width, height)); @@ -106,7 +101,6 @@ private static ViewGroup getParent(View view) { } private void initStickyHeadersHolder() { - mStickyHolderLayout = mAdapter.getStickyHeaderContainer(); if (mStickyHolderLayout == null) { // Create stickyContainer for shadow elevation FrameLayout stickyContainer = new FrameLayout(mRecyclerView.getContext()); @@ -119,12 +113,10 @@ private void initStickyHeadersHolder() { mStickyHolderLayout = (ViewGroup) LayoutInflater.from(mRecyclerView.getContext()).inflate(R.layout.sticky_header_layout, stickyContainer); if (FlexibleAdapter.DEBUG) Log.i(TAG, "Default StickyHolderLayout initialized"); } else if (FlexibleAdapter.DEBUG) { - Log.i(TAG, "User StickyHolderLayout initialized"); + Log.i(TAG, "User defined StickyHolderLayout initialized"); } // Show sticky header if exists already updateOrClearHeader(false); - mStickyHolderLayout.setAlpha(0); - mStickyHolderLayout.animate().alpha(1).start(); } private boolean hasStickyHeaderTranslated(int position) { @@ -154,10 +146,11 @@ public void updateOrClearHeader(boolean updateHeaderContent) { private void updateHeader(int headerPosition, boolean updateHeaderContent) { // Check if there is a new header to be sticky if (mHeaderPosition != headerPosition) { -// int firstVisibleItemPosition = Utils.findFirstVisibleItemPosition(mRecyclerView.getLayoutManager()); + // #244 - Don't animate if header is already visible at the first layout position + int firstVisibleItemPosition = Utils.findFirstVisibleItemPosition(mRecyclerView.getLayoutManager()); // Animate if headers were hidden, but don't if configuration changed (rotation) - if (displayWithAnimation && mHeaderPosition == RecyclerView.NO_POSITION) { -// headerPosition != firstVisibleItemPosition) { + if (displayWithAnimation && mHeaderPosition == RecyclerView.NO_POSITION && + headerPosition != firstVisibleItemPosition) { displayWithAnimation = false; mStickyHolderLayout.setAlpha(0); mStickyHolderLayout.animate().alpha(1).start(); @@ -177,10 +170,10 @@ private void updateHeader(int headerPosition, boolean updateHeaderContent) { } private void translateHeader() { + // Sticky at zero offset (no translation) int headerOffsetX = 0, headerOffsetY = 0; - float elevation = 21f; //Default elevation 6dp = 3.5 for each dp - if (Utils.hasLollipop()) - elevation = mStickyHeaderViewHolder.getContentView().getElevation(); + // Get user defined elevation + float elevation = ViewCompat.getElevation(mStickyHeaderViewHolder.getContentView()); // Search for the position where the next header item is found and translate the new offset for (int i = 0; i < mRecyclerView.getChildCount(); i++) { @@ -213,9 +206,9 @@ private void translateHeader() { } } } - // Apply the calculated elevation + // Apply the user elevation to the sticky container ViewCompat.setElevation(mStickyHolderLayout, elevation); - // Apply translation + // Apply translation (pushed up by another header) mStickyHolderLayout.setTranslationX(headerOffsetX); mStickyHolderLayout.setTranslationY(headerOffsetY); // Log.v(TAG, "TranslationX=" + headerOffsetX + " TranslationY=" + headerOffsetY); @@ -252,7 +245,7 @@ private void ensureHeaderParent() { private void resetHeader(FlexibleViewHolder header) { final View view = header.getContentView(); removeViewFromParent(view); - // Reset transformation on removed header + // Reset translation on removed header view.setTranslationX(0); view.setTranslationY(0); if (!header.itemView.equals(view)) @@ -282,6 +275,7 @@ public void onAnimationStart(Animator animation) { @Override public void onAnimationEnd(Animator animation) { + displayWithAnimation = true; //This helps after clearing filter mStickyHolderLayout.setAlpha(0); clearHeader(); } From f4127b70d4df546b81554f86c26043282db7af11 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 4 Dec 2016 15:55:07 +0100 Subject: [PATCH 58/92] Resolves #200 Refactored methods in FlexibleAdapter --- .../flexibleadapter/ExampleAdapter.java | 6 +- .../samples/flexibleadapter/MainActivity.java | 13 +- .../fragments/FragmentAnimators.java | 4 +- .../fragments/FragmentEndlessScrolling.java | 17 +- .../FragmentExpandableMultiLevel.java | 8 +- .../fragments/FragmentExpandableSections.java | 6 +- .../fragments/FragmentHeadersSections.java | 15 +- .../fragments/FragmentHolderSections.java | 14 +- .../fragments/FragmentInstagramHeaders.java | 10 +- .../fragments/FragmentSelectionModes.java | 4 +- .../fragments/FragmentStaggeredLayout.java | 97 +++++---- .../fragments/FragmentViewPager.java | 38 ++-- .../items/ScrollableExpandableItem.java | 2 +- .../items/ScrollableFooterItem.java | 2 +- .../items/ScrollableLayoutItem.java | 2 +- .../items/ScrollableULSItem.java | 2 +- .../items/ScrollableUseCaseItem.java | 2 +- .../res/layout/fragment_recycler_view.xml | 2 +- .../main/res/layout/fragment_view_pager.xml | 10 +- .../flexibleadapter/AnimatorAdapter.java | 22 +-- .../flexibleadapter/FlexibleAdapter.java | 187 +++++++++++------- 21 files changed, 241 insertions(+), 222 deletions(-) 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 e212ec8b..2c05310e 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 @@ -74,7 +74,7 @@ public boolean isEmpty() { * 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() { + public void showLayoutInfo(boolean scrollToPosition) { if (!hasSearchText()) { final ScrollableLayoutItem item = new ScrollableLayoutItem("LAY-L"); if (mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) { @@ -90,10 +90,10 @@ public void showLayoutInfo() { R.string.columns, String.valueOf(Utils.getSpanCount(mRecyclerView.getLayoutManager()))) ); - // NOTE: If you have to change at runtime the LayoutManager and add + // 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, 0L, true); + addScrollableHeaderWithDelay(item, 0L, scrollToPosition); removeScrollableHeaderWithDelay(item, 4000L); } } 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 650315b8..099c7a8f 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 @@ -633,15 +633,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); 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 d964a307..9a54f030 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 @@ -116,7 +116,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); // Add 1 Scrollable Header - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(savedInstanceState == null); } @Override @@ -130,7 +130,7 @@ public void performFabAction() { @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(false); } @Override 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 9d2a64f0..f316e7b9 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 @@ -69,14 +69,14 @@ public void onActivityCreated(Bundle savedInstanceState) { if (savedInstanceState == null) { DatabaseService.getInstance().createEndlessDatabase(0); //N. of items } - initializeRecyclerView(); + initializeRecyclerView(savedInstanceState); // Settings for FlipView FlipView.stopLayoutAnimation(); } @SuppressWarnings({"ConstantConditions", "NullableProblems"}) - private void initializeRecyclerView() { + 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()); @@ -101,8 +101,7 @@ private void initializeRecyclerView() { // 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 - .setDisplayHeadersAtStartUp(true); //Show Headers at startUp! + .setSwipeEnabled(true); //Enable swipe items SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout); swipeRefreshLayout.setEnabled(true); @@ -110,19 +109,19 @@ private void initializeRecyclerView() { // EndlessScrollListener - OnLoadMore (v5.0.0) mAdapter.setEndlessScrollListener(this, new ProgressItem()) -// .setEndlessPageSize(3) - .setEndlessTargetCount(15); -// .setEndlessScrollThreshold(1); //Default=1 + //.setEndlessPageSize(3) //Endless is automatically disabled if newItems < 3 + .setEndlessTargetCount(15); //Endless is automatically disabled if totalItems >= 15 + //.setEndlessScrollThreshold(1); //Default=1 // Add 1 Scrollable Header and 1 Footer items - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(savedInstanceState == null); mAdapter.addScrollableFooter(); } @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(false); } /** 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 5b79a1f9..dc0ab1bb 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 @@ -85,8 +85,8 @@ private void initializeRecyclerView(Bundle savedInstanceState) { // 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! + .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); @@ -94,13 +94,13 @@ private void initializeRecyclerView(Bundle savedInstanceState) { // Add 2 Scrollable Headers mAdapter.addUserLearnedSelection(savedInstanceState == null); - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(savedInstanceState == null); } @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(false); } @Override 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 b9bd9892..35f16421 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 @@ -94,20 +94,20 @@ private void initializeRecyclerView(Bundle savedInstanceState) { // 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) + //.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 1 Scrollable Header - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(savedInstanceState == null); } @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(false); } @Override 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 3c6b5cf4..a07ccfe4 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 @@ -102,7 +102,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { .setUnlinkAllItemsOnRemoveHeaders(true) // Show Headers at startUp, 1st call, correctly executed, no warning log message! .setDisplayHeadersAtStartUp(true) - .enableStickyHeaders() + .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! @@ -114,17 +114,8 @@ private void initializeRecyclerView(Bundle savedInstanceState) { // Add 2 Scrollable Headers and 1 Footer mAdapter.addUserLearnedSelection(savedInstanceState == null); - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(savedInstanceState == null); mAdapter.addScrollableFooter(); - -// mRecyclerView.postDelayed(new Runnable() { -// @Override -// public void run() { -// IHeader newHeader = DatabaseService.newHeader(555); -// SimpleItem iSectionable = DatabaseService.newSimpleItem(999, newHeader); -// mAdapter.addItem(0, iSectionable); -// } -// }, 5000L); } @Override @@ -136,7 +127,7 @@ public void performFabAction() { @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(false); } @Override 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 index 92b5d630..9b1d8bf4 100644 --- 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 @@ -45,36 +45,36 @@ public FragmentHolderSections() { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - //Create New Database and Initialize RecyclerView + // 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()' + // 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 + // 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!!! + // 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) - .enableStickyHeaders(); + .setStickyHeaders(true); SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout); swipeRefreshLayout.setEnabled(true); mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); - //Add 1 Scrollable Header + // Add 1 Scrollable Header mAdapter.addUserLearnedSelection(savedInstanceState == null); } 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 cb41d1c4..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 @@ -66,11 +66,11 @@ public void onActivityCreated(Bundle savedInstanceState) { @SuppressWarnings({"unchecked", "ConstantConditions"}) private void initializeRecyclerView() { - //Initialize Adapter and RecyclerView - //true = it makes use of stableIds, I strongly suggest to implement 'item.hashCode()' + // 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.addListener(getActivity()) - //Experimenting NEW features (v5.0.0) + // Experimenting NEW features (v5.0.0) .setAnimationOnScrolling(true) .setAnimationOnReverseScrolling(true); mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view); @@ -84,7 +84,7 @@ private void initializeRecyclerView() { mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), 0, 24)); mAdapter.setDisplayHeadersAtStartUp(true) //Show Headers at startUp! - .enableStickyHeaders() //Make headers sticky + .setStickyHeaders(true) //Make headers sticky // Endless scroll with 1 item threshold .setEndlessScrollListener(this, new ProgressItem()) .setEndlessScrollThreshold(1); //Default=1 @@ -132,7 +132,7 @@ public void onLoadMore(int lastPosition, int currentPage) { 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)); 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 feedcccb..11c68e25 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 @@ -111,13 +111,13 @@ public void run() { // Add 2 Scrollable Headers mAdapter.addUserLearnedSelection(savedInstanceState == null); - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(savedInstanceState == null); } @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(); + 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 483e6007..00ba4f72 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 @@ -55,48 +55,47 @@ 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()' + // 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); 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 SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout); swipeRefreshLayout.setEnabled(true); mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); - //Add 1 Scrollable Header - mAdapter.showLayoutInfo(); + // Add 1 Scrollable Header + mAdapter.showLayoutInfo(savedInstanceState == null); } @Override @@ -107,7 +106,7 @@ public int getContextMenuResId() { @Override public void showNewLayoutInfo(MenuItem item) { super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(); + mAdapter.showLayoutInfo(false); } @Override @@ -127,9 +126,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 +142,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 +180,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 +201,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 +236,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 +246,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 index 42ff14ef..b81f47c0 100644 --- 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 @@ -64,7 +64,7 @@ public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "Creating new Fragment for Section " + mSection); } - //Contribution for specific action buttons in the Toolbar + // Contribution for specific action buttons in the Toolbar setHasOptionsMenu(true); } @@ -77,40 +77,39 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - //Settings for FlipView + // Settings for FlipView FlipView.resetLayoutAnimationDelay(true, 1000L); - //Initialize RecyclerView + // Initialize RecyclerView initializeRecyclerView(); - //Settings for FlipView + // Settings for FlipView FlipView.stopLayoutAnimation(); } private void initializeRecyclerView() { - //Initialize Adapter and RecyclerView - //Use of stableIds, I strongly suggest to implement 'item.hashCode()' + // 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) + // 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 + // 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!!! + // 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//.setStickyHeaderContainer((ViewGroup) getView().findViewById(R.id.sticky_header_container)) - .setDisplayHeadersAtStartUp(true) - .enableStickyHeaders(); + // Sticky Headers + mAdapter.setDisplayHeadersAtStartUp(true) + .setStickyHeaders(true); } private List createList(int size, int headers) { @@ -127,12 +126,12 @@ private List createList(int size, int headers) { @Override public void onPrepareOptionsMenu(Menu menu) { - //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()); @@ -145,13 +144,8 @@ public void onPrepareOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_sticky_headers) { - if (mAdapter.areHeadersSticky()) { - item.setChecked(false); - mAdapter.disableStickyHeaders(); - } else { - item.setChecked(true); - mAdapter.enableStickyHeaders(); - } + item.setChecked(!mAdapter.areHeadersSticky()); + mAdapter.setStickyHeaders(!mAdapter.areHeadersSticky()); } else if (id == R.id.action_show_hide_headers) { if (mAdapter.areHeadersShown()) { mAdapter.hideAllHeaders(); 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 index 03e84e61..600bca7d 100644 --- 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 @@ -107,6 +107,6 @@ public void scrollAnimators(@NonNull List animators, int position, boo @Override public String toString() { - return "LayoutItem[" + super.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 index a2a53166..cc96c0b9 100644 --- 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 @@ -111,7 +111,7 @@ public boolean animateRemoveImpl(ViewPropertyAnimatorListener listener, long rem @Override public String toString() { - return "FooterItem[" + super.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 index 92ee873e..59b49515 100644 --- 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 @@ -68,6 +68,6 @@ public void scrollAnimators(@NonNull List animators, int position, boo @Override public String toString() { - return "LayoutItem[" + super.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/ScrollableULSItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java index 179a5f4e..c2493822 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java @@ -89,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 index 3c48672f..ce6c1c40 100644 --- 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 @@ -83,7 +83,7 @@ public void scrollAnimators(@NonNull List animators, int position, boo @Override public String toString() { - return "FooterItem[" + super.toString() + "]"; + return "ScrollableUseCaseItem[" + super.toString() + "]"; } } \ 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 31f8921b..4113ab17 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 @@ -13,7 +13,7 @@ android:enabled="false"> --> - - - + - + 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 cac08517..23641823 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -129,7 +129,7 @@ void setScrollAnimate(boolean 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 @@ -143,7 +143,7 @@ public AnimatorAdapter setAnimationInitialDelay(long initialDelay) { } /** - * 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}. * @@ -174,7 +174,7 @@ public AnimatorAdapter setAnimationEntryStep(boolean entryStep) { } /** - * 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 @@ -188,7 +188,7 @@ public AnimatorAdapter setAnimationDuration(@IntRange(from = 1) long duration) { } /** - * 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 @@ -201,7 +201,7 @@ public AnimatorAdapter setAnimationInterpolator(@NonNull Interpolator interpolat } /** - * 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. @@ -216,12 +216,12 @@ public AnimatorAdapter setAnimationStartPosition(@IntRange(from = 0) int start) } /** - * 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 @@ -241,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). * @@ -359,7 +359,7 @@ private void cancelExistingAnimation(final int hashCode) { * @param position the current item position */ @SuppressWarnings("ConstantConditions") - protected void animateView(final RecyclerView.ViewHolder holder, final int position) { + protected final void animateView(final RecyclerView.ViewHolder holder, final int position) { if (mRecyclerView == null) return; // Use always the max child count reached 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 1f08f079..bac8fb5e 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -102,7 +102,7 @@ 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_STICKY = TAG + "_stickyHeaders"; private static final String EXTRA_LEVEL = TAG + "_selectedLevel"; private static final String EXTRA_SEARCH = TAG + "_searchText"; @@ -309,7 +309,6 @@ public FlexibleAdapter addListener(@Nullable Object listener) { @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); - //TODO is this necessary? if (mStickyHeaderHelper != null && headersShown) { mStickyHeaderHelper.attachToRecyclerView(mRecyclerView); } @@ -323,7 +322,6 @@ public void onAttachedToRecyclerView(RecyclerView recyclerView) { */ @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { - //TODO is this necessary? if (mStickyHeaderHelper != null) { mStickyHeaderHelper.detachFromRecyclerView(); mStickyHeaderHelper = null; @@ -952,7 +950,7 @@ public void run() { */ public final void removeScrollableHeaderWithDelay(@NonNull final T headerItem, @IntRange(from = 0) long delay) { if (DEBUG) - Log.d(TAG, "Enqueued removing scrollable header (" + delay + "ms) " + headerItem); + Log.d(TAG, "Enqueued removing scrollable header (" + delay + "ms) " + getClassName(headerItem)); mHandler.postDelayed(new Runnable() { @Override public void run() { @@ -1157,7 +1155,6 @@ public IHeader getHeaderOf(@NonNull T item) { * @return the IHeader item linked to the specified item position * @since 5.0.0-b6 */ - //TODO: rename to getSectionHeaderByPosition? public IHeader getSectionHeader(@IntRange(from = 0) int position) { //Headers are not visible nor sticky if (!headersShown) return null; @@ -1267,9 +1264,10 @@ public boolean areHeadersSticky() { * @return this Adapter, so the call can be chained * @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); } @@ -1277,21 +1275,87 @@ 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) { - if (DEBUG) Log.i(TAG, "Set stickyHeaders=" + sticky + " (in Post!)"); + /** + * 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 of the {@code FlexibleViewHolder}.
        • + *
        • Optionally, you can set a custom sticky layout container that must be already + * inflated. + *
          Check {@link #setStickyHeaders(boolean, ViewGroup)}.
        • + *
        • 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) + * @since 5.0.0-rc1 + */ + public FlexibleAdapter setStickyHeaders(final 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) + * @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() { // Enable or Disable the sticky headers layout if (sticky) { if (mStickyHeaderHelper == null) { - mStickyHeaderHelper = new StickyHeaderHelper(FlexibleAdapter.this, mStickyHeaderChangeListener); + mStickyHeaderHelper = new StickyHeaderHelper(FlexibleAdapter.this, + mStickyHeaderChangeListener, mStickyContainer); mStickyHeaderHelper.attachToRecyclerView(mRecyclerView); if (DEBUG) Log.i(TAG, "Sticky headers enabled"); } @@ -1319,7 +1383,7 @@ public void run() { * * @return ViewGroup layout that will hold the sticky header ItemViews * @since 5.0.0-b6 - * @deprecated Use {@link #getStickyHeaderContainer()} + * @deprecated Unused. Use {@link #setStickyHeaders(boolean, ViewGroup)}. */ @Deprecated public ViewGroup getStickySectionHeadersHolder() { @@ -1338,10 +1402,10 @@ public ViewGroup getStickySectionHeadersHolder() { * * @return ViewGroup layout that will hold the sticky header itemViews * @since 5.0.0-rc1 + * @deprecated Unused, not needed anymore. */ - //TODO: Integrate the usage of the custom container in the new StickyHeaderHelper - //TODO: Review the comment for the new StickyHeaderHelper: layout is not needed anymore - public final ViewGroup getStickyHeaderContainer() { + @Deprecated + public ViewGroup getStickyHeaderContainer() { return mStickyContainer; } @@ -1350,10 +1414,12 @@ public final ViewGroup getStickyHeaderContainer() { * * @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, "StickyHeaderHelper has been already initialized! Call this method before enabling StickyHeaders"); + 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)); @@ -1362,14 +1428,11 @@ public FlexibleAdapter setStickyHeaderContainer(@NonNull ViewGroup stickyContain } /** - * 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! + * 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 @@ -1378,53 +1441,54 @@ public FlexibleAdapter setStickyHeaderContainer(@NonNull ViewGroup stickyContain * @see #setAnimationOnScrolling(boolean) * @since 5.0.0-b6 */ - //TODO: deprecation, rename to displayHeadersAtStartUp() with animate on loading parameter? 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) { if (DEBUG) Log.i(TAG, "showAllHeaders at startup"); - //No notifyItemInserted! + // No notifyItemInserted! showAllHeadersWithReset(true); } else { if (DEBUG) Log.i(TAG, "showAllHeaders with insert notification (in Post!)"); - //In post, let's notifyItemInserted! + // 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, "Double call detected! 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))) { @@ -1440,23 +1504,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; IHeader sameHeader = null; - //resetHiddenStatus();//Necessary after the filter and the update 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++; //It's the same element, skip it position++; } headersShown = true; - multiRange = false; } /** @@ -1475,9 +1537,8 @@ private boolean showHeaderOf(int position, @NonNull T item, boolean init) { if (header.isHidden()) { if (DEBUG) Log.v(TAG, "Showing header at position " + position + " header=" + header); header.setHidden(false); - //Skip notifyItemInserted when init=true and insert the header properly! - if (init) performInsert(position, Collections.singletonList((T) header), false); - else addItem(position, (T) header); + //Insert header, but skip notifyItemInserted when init=true! + performInsert(position, Collections.singletonList((T) header), !init); return true; } return false; @@ -1488,6 +1549,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() { @@ -1543,25 +1605,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 - * @deprecated Not used anymore. - */ - @Deprecated - 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.

        @@ -4805,8 +4848,7 @@ public void onSaveInstanceState(Bundle outState) { outState.putString(EXTRA_SEARCH, this.mSearchText); //Save headers shown status outState.putBoolean(EXTRA_HEADERS, this.headersShown); - //TODO: enableStickyHeaders - //outState.putBoolean(EXTRA_STICKY, areHeadersSticky()); + outState.putBoolean(EXTRA_STICKY, areHeadersSticky()); } } @@ -4826,10 +4868,9 @@ public void onRestoreInstanceState(Bundle savedInstanceState) { showAllHeadersWithReset(true); } this.headersShown = headersShown; - //TODO: enableStickyHeaders -// if (savedInstanceState.getBoolean(EXTRA_STICKY) && !areHeadersSticky()) { -// setStickyHeaders(true); -// } + if (savedInstanceState.getBoolean(EXTRA_STICKY) && !areHeadersSticky()) { + setStickyHeaders(true); + } //Restore selection state super.onRestoreInstanceState(savedInstanceState); if (mScrollableHeaders.size() > 0) { From eb7923702b45019e3dfc05e7549f3f3a92c2791c Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 4 Dec 2016 16:36:53 +0100 Subject: [PATCH 59/92] Resolves #200 Deprecated all marked methods in FlexibleAdapter --- .../flexibleadapter/FlexibleAdapter.java | 76 +++++++++++-------- 1 file changed, 46 insertions(+), 30 deletions(-) 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 bac8fb5e..7edaa9fb 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -557,7 +557,6 @@ public long getItemId(int position) { * @return the total number of items (headers and footers INCLUDED) held by the adapter * @see #getItemCount(boolean) * @see #getItemCountOfTypes(Integer...) - * @see #getItemCountOfTypesUntil(int, Integer...) * @see #isEmpty() * @since 1.0.0 */ @@ -578,8 +577,7 @@ public final int getItemCount() { * @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...) - * @see #getItemCountOfTypesUntil(int, Integer...) + * @see #getItemCountOfTypes(Integer...)) * @since 5.0.0-rc1 */ //TODO: Rename to getMainItemCount? @@ -595,12 +593,17 @@ public final int getItemCount(boolean withHeadersFooters) { * @return number of the viewTypes counted * @see #getItemCount() * @see #getItemCount(boolean) - * @see #getItemCountOfTypesUntil(int, Integer...) * @see #isEmpty() * @since 5.0.0-b1 */ public final int getItemCountOfTypes(Integer... viewTypes) { - return getItemCountOfTypesUntil(getItemCount(), viewTypes); + List viewTypeList = Arrays.asList(viewTypes); + int count = 0; + for (int i = 0; i < getItemCount(); i++) { + if (viewTypeList.contains(getItemViewType(i))) + count++; + } + return count; } /** @@ -614,8 +617,9 @@ public final int getItemCountOfTypes(Integer... viewTypes) { * @see #getItemCountOfTypes(Integer...) * @see #isEmpty() * @since 5.0.0-b5 + * @deprecated Not used anymore. */ - //TODO: deprecation? + @Deprecated public final int getItemCountOfTypesUntil(@IntRange(from = 0) int position, Integer... viewTypes) { List viewTypeList = Arrays.asList(viewTypes); int count = 0; @@ -999,8 +1003,10 @@ private void restoreScrollableHeadersAndFooters(List items) { * @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; } @@ -1013,8 +1019,10 @@ 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; @@ -1043,8 +1051,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; @@ -1059,8 +1069,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) { @@ -1075,8 +1087,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()) { @@ -1173,8 +1187,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); @@ -1187,8 +1203,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++) { @@ -1557,10 +1575,6 @@ public void hideAllHeaders() { @Override public void run() { multiRange = true; - //Hide orphan headers first - for (IHeader header : getOrphanHeaders()) { - hideHeader(getGlobalPositionOf(header), header); - } //Hide linked headers between Scrollable Headers and Footers int position = getItemCount() - mScrollableFooters.size() - 1; while (position >= Math.max(0, mScrollableHeaders.size() - 1)) { @@ -1672,7 +1686,7 @@ 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) if (!mOrphanHeaders.contains(header) && !isHeaderShared(header, positionStart, itemCount)) { @@ -1682,13 +1696,13 @@ 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 < getItemCount() - mScrollableFooters.size(); i++) { @@ -2855,8 +2869,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! */ - //TODO: Deprecation, this is like a command to expand + @Deprecated public int addAllSubItemsFrom(@IntRange(from = 0) int parentPosition, @NonNull IExpandable parent, boolean expandParent, @Nullable Object payload) { @@ -3332,7 +3348,7 @@ public void removeRange(@IntRange(from = 0) int positionStart, return; } - //Handle header linkage + // Update content of the header linked to first item of the range T item = getItem(positionStart); IHeader header = getHeaderOf(item); int headerPosition = getGlobalPositionOf(header); @@ -3360,14 +3376,14 @@ public void removeRange(@IntRange(from = 0) int positionStart, 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 From 5695cf96d57147011072ecb6c0340ab7ef10d757 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 4 Dec 2016 17:22:48 +0100 Subject: [PATCH 60/92] Resolves #200 Renamed getItemCount(boolean) to getMainItemCount() --- .../fragments/FragmentEndlessScrolling.java | 2 +- .../flexibleadapter/FlexibleAdapter.java | 55 ++++++++++--------- 2 files changed, 30 insertions(+), 27 deletions(-) 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 f316e7b9..07875376 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 @@ -140,7 +140,7 @@ public void showNewLayoutInfo(MenuItem item) { 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.getItemCount(false)); + Log.d(TAG, "Total items loaded=" + mAdapter.getMainItemCount()); } /** 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 7edaa9fb..0f2b4d8c 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -295,7 +295,7 @@ public FlexibleAdapter addListener(@Nullable Object listener) { if (listener instanceof OnUpdateListener) { if (DEBUG) Log.i(TAG, "- OnUpdateListener"); mUpdateListener = (OnUpdateListener) listener; - mUpdateListener.onUpdateEmptyView(getItemCount(false)); + mUpdateListener.onUpdateEmptyView(getMainItemCount()); } return this; } @@ -475,8 +475,9 @@ public boolean isAnyChildSelected() { /*--------------*/ /** - * Convenience method of {@link #updateDataSet(List, boolean)}. - *

        In this case changes will NOT be animated: Warning! + * 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 @@ -491,9 +492,14 @@ public void updateDataSet(List items) { /** * 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! - *

        Note: Scrollable Headers and Footers (if existent) will be restored in this call.

        + * 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: *
        1. {@link #expandItemsAtStartUp()}
        2. *
        3. {@link #showAllHeaders()} if headers are shown
        4. @@ -549,13 +555,13 @@ public long getItemId(int position) { /** * Returns the total number of items in the data set held by the adapter (headers and footers - * INCLUDED). Use {@link #getItemCount(boolean)} with {@code false} as parameter to retrieve + * 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 items (headers and footers INCLUDED) held by the adapter - * @see #getItemCount(boolean) + * @see #getMainItemCount() * @see #getItemCountOfTypes(Integer...) * @see #isEmpty() * @since 1.0.0 @@ -573,17 +579,14 @@ public final int getItemCount() { *
      * Note: This method cannot be overridden since internal methods rely on it. * - * @param withHeadersFooters true to count also headers and footers, false to count only real items * @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 */ - //TODO: Rename to getMainItemCount? - public final int getItemCount(boolean withHeadersFooters) { - return withHeadersFooters ? getItemCount() : - getItemCount() - mScrollableHeaders.size() - mScrollableFooters.size(); + public final int getMainItemCount() { + return getItemCount() - mScrollableHeaders.size() - mScrollableFooters.size(); } /** @@ -592,7 +595,7 @@ public final int getItemCount(boolean withHeadersFooters) { * @param viewTypes the viewTypes to count * @return number of the viewTypes counted * @see #getItemCount() - * @see #getItemCount(boolean) + * @see #getMainItemCount() * @see #isEmpty() * @since 5.0.0-b1 */ @@ -613,7 +616,7 @@ public final int getItemCountOfTypes(Integer... viewTypes) { * @param position the position limit where to stop counting (included) * @param viewTypes the viewTypes to count * @see #getItemCount() - * @see #getItemCount(boolean) + * @see #getMainItemCount() * @see #getItemCountOfTypes(Integer...) * @see #isEmpty() * @since 5.0.0-b5 @@ -1860,7 +1863,7 @@ public boolean isEndlessScrollEnabled() { * @since 5.0.0-rc1 */ public int getEndlessCurrentPage() { - return Math.max(1, mEndlessPageSize > 0 ? getItemCount(false) / mEndlessPageSize : 0); + return Math.max(1, mEndlessPageSize > 0 ? getMainItemCount() / mEndlessPageSize : 0); } /** @@ -2026,7 +2029,7 @@ public void run() { // 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(getItemCount(false), getEndlessCurrentPage()); + mEndlessScrollListener.onLoadMore(getMainItemCount(), getEndlessCurrentPage()); } } }); @@ -2065,7 +2068,7 @@ public void onLoadMoreComplete(@Nullable List newItems) { public void onLoadMoreComplete(@Nullable List newItems, @IntRange(from = -1) long delay) { // 1. Calculate new items count int newItemsSize = newItems == null ? 0 : newItems.size(); - int totalItemCount = newItemsSize + getItemCount(false); + int totalItemCount = newItemsSize + getMainItemCount(); // 2. Add any new items if (newItemsSize > 0) { if (DEBUG) @@ -2721,7 +2724,7 @@ public void run() { /** * Simply append the provided item to the end of the list. *

      Convenience method of {@link #addItem(int, IFlexible)} with - * {@code position = getItemCount()}.

      + * {@code position = getMainItemCount()}.

      * * @param item the item to add * @return true if the internal list was successfully modified, false otherwise @@ -2773,7 +2776,7 @@ public boolean addItems(@IntRange(from = 0) int position, @NonNull List items Log.e(TAG, "addItems No items to add!"); return false; } - int initialCount = getItemCount(false);//Count only main 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; @@ -2790,7 +2793,7 @@ public boolean addItems(@IntRange(from = 0) int position, @NonNull List items } //Call listener to update EmptyView if (!recursive && mUpdateListener != null && !multiRange && initialCount == 0 && getItemCount() > 0) - mUpdateListener.onUpdateEmptyView(getItemCount(false)); + mUpdateListener.onUpdateEmptyView(getMainItemCount()); return true; } @@ -3415,7 +3418,7 @@ public void removeRange(@IntRange(from = 0) int positionStart, //Update empty view if (mUpdateListener != null && !multiRange && initialCount > 0 && getItemCount() == 0) - mUpdateListener.onUpdateEmptyView(getItemCount(false)); + mUpdateListener.onUpdateEmptyView(getMainItemCount()); } /** @@ -3599,7 +3602,7 @@ public void restoreDeletedItems() { //Call listener to update EmptyView multiRange = false; if (mUpdateListener != null && initialCount == 0 && getItemCount() > 0) - mUpdateListener.onUpdateEmptyView(getItemCount(false)); + mUpdateListener.onUpdateEmptyView(getMainItemCount()); emptyBin(); } @@ -4916,7 +4919,7 @@ public interface OnUpdateListener { * will represents only the main items.

      * * @param size the current number of main items in the adapter, result of - * {@link FlexibleAdapter#getItemCount(boolean)} + * {@link FlexibleAdapter#getMainItemCount()} * @since 5.0.0-b1 */ void onUpdateEmptyView(int size); @@ -5288,7 +5291,7 @@ private void postUpdate(boolean init) { onPostUpdate(); //Update empty view if (mUpdateListener != null) { - mUpdateListener.onUpdateEmptyView(getItemCount(false)); + mUpdateListener.onUpdateEmptyView(getMainItemCount()); } } @@ -5311,7 +5314,7 @@ private void postFilter() { onPostFilter(); //Call listener to update EmptyView, assuming the filter always made a change if (mUpdateListener != null) - mUpdateListener.onUpdateEmptyView(getItemCount(hasSearchText())); + mUpdateListener.onUpdateEmptyView(getMainItemCount()); } /** From de064b8f12f2a1edb9c9d37ff227b923f8b66279 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 4 Dec 2016 17:34:33 +0100 Subject: [PATCH 61/92] Removed from demoApp, some calls of deprecated methods --- .../davidea/samples/flexibleadapter/MainActivity.java | 11 ----------- .../fragments/FragmentExpandableMultiLevel.java | 3 +-- .../fragments/FragmentExpandableSections.java | 1 - .../fragments/FragmentHeadersSections.java | 3 +-- .../eu/davidea/flexibleadapter/FlexibleAdapter.java | 1 - 5 files changed, 2 insertions(+), 17 deletions(-) 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 099c7a8f..0b8d9387 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 @@ -51,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; @@ -811,7 +810,6 @@ public boolean onPreAction() { .withAction(UndoHelper.ACTION_REMOVE, new UndoHelper.SimpleActionListener() { @Override public void onPostAction() { - logOrphanHeaders(); //Handle ActionMode title if (mAdapter.getSelectedItemCount() == 0) mActionModeHelper.destroyActionModeIfCan(); @@ -976,7 +974,6 @@ public void onPostAction() { mRefreshHandler.sendEmptyMessageDelayed(0, 20000); //Finish the action mode mActionModeHelper.destroyActionModeIfCan(); - logOrphanHeaders(); } }) .remove(mAdapter.getSelectedPositions(), @@ -1074,14 +1071,6 @@ public void onBackPressed() { 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 AbstractItem) { AbstractItem exampleItem = (AbstractItem) flexibleItem; 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 dc0ab1bb..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 @@ -69,8 +69,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mAdapter.expandItemsAtStartUp() .setAutoCollapseOnExpand(false) .setMinCollapsibleLevel(1) //Auto-collapse only items with level >= 1 (avoid to collapse also sections!) - .setAutoScrollOnExpand(true) - .setRemoveOrphanHeaders(false); + .setAutoScrollOnExpand(true); mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view); mRecyclerView.setLayoutManager(createNewLinearLayoutManager()); mRecyclerView.setAdapter(mAdapter); 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 35f16421..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 @@ -74,7 +74,6 @@ private void initializeRecyclerView(Bundle savedInstanceState) { .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) .setAnimationOnScrolling(DatabaseConfiguration.animateOnScrolling) .setAnimationOnReverseScrolling(true); mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view); 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 a07ccfe4..5d3ca98d 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 @@ -82,8 +82,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { // 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 + 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()); 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 0f2b4d8c..c3da14ab 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -3331,7 +3331,6 @@ public void removeRange(@IntRange(from = 0) int positionStart, * @see #removeRange(int, int) * @see #removeAllSelectedItems(Object) * @see #setPermanentDelete(boolean) - * @see #setRemoveOrphanHeaders(boolean) * @see #setRestoreSelectionOnUndo(boolean) * @see #getDeletedItems() * @see #getDeletedChildren(IExpandable) From 5ec546123bdaa574a5e968e65aeada5fdf884368 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 4 Dec 2016 18:54:44 +0100 Subject: [PATCH 62/92] Customized sticky layout elevation --- .../main/res/layout/recycler_header_item.xml | 2 +- .../flexibleadapter/FlexibleAdapter.java | 10 +++++---- .../helpers/StickyHeaderHelper.java | 21 ++++++++++++++----- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/flexible-adapter-app/src/main/res/layout/recycler_header_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_header_item.xml index ea265cb1..ddd12922 100644 --- a/flexible-adapter-app/src/main/res/layout/recycler_header_item.xml +++ b/flexible-adapter-app/src/main/res/layout/recycler_header_item.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="?android:attr/listPreferredItemHeightSmall" android:background="#dd7e57c2" - android:elevation="6dp"> + android:elevation="5dp"> 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 of the {@code FlexibleViewHolder}.
    • - *
    • Optionally, you can set a custom sticky layout container that must be already - * inflated. + * 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 @@ -1344,7 +1346,7 @@ public void disableStickyHeaders() { * @see #setDisplayHeadersAtStartUp(boolean) * @since 5.0.0-rc1 */ - public FlexibleAdapter setStickyHeaders(final boolean sticky) { + public FlexibleAdapter setStickyHeaders(boolean sticky) { return setStickyHeaders(sticky, mStickyContainer); } diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index ad89d29b..8f8dcf75 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -54,7 +54,7 @@ public final class StickyHeaderHelper extends OnScrollListener { private OnStickyHeaderChangeListener mStickyHeaderChangeListener; private int mHeaderPosition = RecyclerView.NO_POSITION; private boolean displayWithAnimation = false; - + private float mElevation; public StickyHeaderHelper(FlexibleAdapter adapter, OnStickyHeaderChangeListener stickyHeaderChangeListener, @@ -169,11 +169,23 @@ private void updateHeader(int headerPosition, boolean updateHeaderContent) { translateHeader(); } + private void configureLayoutElevation() { + // Needed to elevate the view + //TODO: set custom background for transparency (elevation will be lost!) + mStickyHolderLayout.setBackgroundColor(Color.WHITE); + // 1. Take elevation from header item layout (most important) + mElevation = ViewCompat.getElevation(mStickyHeaderViewHolder.getContentView()); + if (mElevation == 0f) { + // 2. Default elevation + mElevation = 21f; + } + } + private void translateHeader() { // Sticky at zero offset (no translation) int headerOffsetX = 0, headerOffsetY = 0; - // Get user defined elevation - float elevation = ViewCompat.getElevation(mStickyHeaderViewHolder.getContentView()); + // Get calculated elevation + float elevation = mElevation; // Search for the position where the next header item is found and translate the new offset for (int i = 0; i < mRecyclerView.getChildCount(); i++) { @@ -238,8 +250,7 @@ private void ensureHeaderParent() { params.height = view.getLayoutParams().height; removeViewFromParent(view); mStickyHolderLayout.addView(view); - //TODO: set custom background for transparency (elevation will be lost!) - mStickyHolderLayout.setBackgroundColor(Color.WHITE);// Needed to elevate the view + configureLayoutElevation(); } private void resetHeader(FlexibleViewHolder header) { From 2a8fdc22d27f39abbfd02870fe7a0d168839e452 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 4 Dec 2016 19:09:34 +0100 Subject: [PATCH 63/92] Better comment for Endless feature. --- .../java/eu/davidea/samples/flexibleadapter/MainActivity.java | 4 ++-- .../flexibleadapter/fragments/FragmentEndlessScrolling.java | 3 ++- .../main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) 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 0b8d9387..07ed3a8a 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 @@ -551,7 +551,7 @@ public boolean onPrepareOptionsMenu(Menu menu) { 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); @@ -582,7 +582,7 @@ public boolean onOptionsItemSelected(MenuItem item) { 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(); 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 07875376..97f45b27 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 @@ -177,7 +177,8 @@ public void run() { // 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 + // - 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! 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 da757c1f..141f975d 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -2053,7 +2053,8 @@ public void onLoadMoreComplete(@Nullable List newItems) { /** * 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 {@code bindViewHolder} of the item. *

      A {@link #notifyItemChanged(int, Object)} with payload {@link Payload#NO_MORE_LOAD} From 0320868e9319c87926e645cd620f93ab12b9ba3b Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 4 Dec 2016 21:09:11 +0100 Subject: [PATCH 64/92] Added StickyHeaderElevation setting --- .../flexibleadapter/FlexibleAdapter.java | 33 +++++++++++++++++++ .../helpers/StickyHeaderHelper.java | 4 +-- 2 files changed, 35 insertions(+), 2 deletions(-) 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 141f975d..1c73c233 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -22,6 +22,7 @@ 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; @@ -134,6 +135,7 @@ public class FlexibleAdapter /* Section items (with sticky headers) */ private List mOrphanHeaders; private boolean headersShown = false, recursive = false; + private float mStickyElevation; private StickyHeaderHelper mStickyHeaderHelper; private ViewGroup mStickyContainer; @@ -1344,6 +1346,7 @@ public void disableStickyHeaders() { * @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) { @@ -1361,6 +1364,7 @@ public FlexibleAdapter setStickyHeaders(boolean sticky) { * @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) { @@ -1392,6 +1396,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: diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index 8f8dcf75..b05801f9 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -176,8 +176,8 @@ private void configureLayoutElevation() { // 1. Take elevation from header item layout (most important) mElevation = ViewCompat.getElevation(mStickyHeaderViewHolder.getContentView()); if (mElevation == 0f) { - // 2. Default elevation - mElevation = 21f; + // 2. Take elevation settings + mElevation = mAdapter.getStickyHeaderElevation(); } } From 56f72732d1b6a022b492a42d5c8d05838f70d8f5 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 12 Dec 2016 23:29:10 +0100 Subject: [PATCH 65/92] Scrollable Headers and Footers image --- .../flexibleadapter/FlexibleAdapter.java | 1 + screenshots/shf.png | Bin 0 -> 28040 bytes 2 files changed, 1 insertion(+) create mode 100644 screenshots/shf.png 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 1c73c233..df92c219 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -93,6 +93,7 @@ *
      20/02/2016 Sticky headers *
      22/04/2016 Endless Scrolling *
      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 diff --git a/screenshots/shf.png b/screenshots/shf.png new file mode 100644 index 0000000000000000000000000000000000000000..12c8e89d87f3c1025e6baaa9bf749b2d58c20b5b GIT binary patch literal 28040 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

      X7?NA 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 uFD@k73S@{8^k6STd%|*e? Date: Mon, 12 Dec 2016 23:45:33 +0100 Subject: [PATCH 66/92] Scrollable Headers and Footers image --- .../res/layout/recycler_vertical_item.xml | 74 ++++++++++++++++++ screenshots/shf.png | Bin 28040 -> 79768 bytes 2 files changed, 74 insertions(+) create mode 100644 flexible-adapter-app/src/main/res/layout/recycler_vertical_item.xml 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/screenshots/shf.png b/screenshots/shf.png index 12c8e89d87f3c1025e6baaa9bf749b2d58c20b5b..8b8e91dc891a743f1049e2033c9750f7f6bc6846 100644 GIT binary patch literal 79768 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 literal 28040 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

      X7?NA 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 uFD@k73S@{8^k6STd%|*e? Date: Mon, 12 Dec 2016 23:55:10 +0100 Subject: [PATCH 67/92] Scrollable Headers and Footers image --- screenshots/shf.png | Bin 79768 -> 78442 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/shf.png b/screenshots/shf.png index 8b8e91dc891a743f1049e2033c9750f7f6bc6846..5460a69bd09ecb8efc7eedcd23ac9ed58e3a9a89 100644 GIT binary patch literal 78442 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 From b6b813bbd797a8f791ca4dbb64e66aada6583eac Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Tue, 13 Dec 2016 23:28:58 +0100 Subject: [PATCH 68/92] Update README.md --- README.md | 92 ++++++++++--------- build.gradle | 2 +- .../flexibleadapter/FlexibleAdapter.java | 2 +- .../common/FlexibleItemAnimator.java | 12 +-- .../davidea/flexibleadapter/utils/Utils.java | 2 +- 5 files changed, 56 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index a039260d..06aaeb45 100644 --- a/README.md +++ b/README.md @@ -5,37 +5,39 @@ # 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 2016.12.xx (157KB) +- 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. -#### 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 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 +45,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 +101,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) - 2016.12.xx ###### 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,17 +135,19 @@ 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)
      +It will be a pleasure to add your App here, once it is published. + +[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 96fcf2dc..b87b5043 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' + 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/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java index df92c219..5d78aef9 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -585,7 +585,7 @@ public final int getItemCount() { * @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...)) + * @see #getItemCountOfTypes(Integer...) * @since 5.0.0-rc1 */ public final int getMainItemCount() { diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/common/FlexibleItemAnimator.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/common/FlexibleItemAnimator.java index befe8248..aad73c32 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/common/FlexibleItemAnimator.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/common/FlexibleItemAnimator.java @@ -791,14 +791,10 @@ private static void clear(View v) { * If the payload list is not empty, DefaultItemAnimator returns true. * When this is the case: *

        - *
      • If you override {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, both - * ViewHolder arguments will be the same instance. - *
      • - *
      • - * If you are not overriding {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, - * then DefaultItemAnimator will call {@link #animateMove(ViewHolder, int, int, int, int)} and - * run a move animation instead. - *
      • + *
      • If you override {@code animateChange()}, both ViewHolder arguments will be the same + * instance.
      • + *
      • If you are not overriding {@code animateChange()}, then DefaultItemAnimator will call + * {@code animateMove()} and run a move animation instead.
      • *
      */ @Override diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java index ed90decf..ea45a53a 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java @@ -69,7 +69,7 @@ public static boolean hasJellyBean() { } /** - * @return the string representation of the provided {@link SelectableAdapter.Mode} + * @return the string representation of the provided {@link eu.davidea.flexibleadapter.SelectableAdapter.Mode} * @since 5.0.0-rc1 */ @SuppressLint("SwitchIntDef") From 2561c7e44f6a47d97bf54e56a4802b63367328dc Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 14 Dec 2016 14:48:33 +0100 Subject: [PATCH 69/92] Resolves #248 Transparent sticky headers based on elevation value and current background --- .../flexibleadapter/helpers/StickyHeaderHelper.java | 10 +++++----- .../java/eu/davidea/viewholders/ContentViewHolder.java | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index b05801f9..a9b787d4 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -16,7 +16,6 @@ package eu.davidea.flexibleadapter.helpers; import android.animation.Animator; -import android.graphics.Color; import android.support.v4.view.ViewCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.OrientationHelper; @@ -170,15 +169,16 @@ private void updateHeader(int headerPosition, boolean updateHeaderContent) { } private void configureLayoutElevation() { - // Needed to elevate the view - //TODO: set custom background for transparency (elevation will be lost!) - mStickyHolderLayout.setBackgroundColor(Color.WHITE); // 1. Take elevation from header item layout (most important) mElevation = ViewCompat.getElevation(mStickyHeaderViewHolder.getContentView()); if (mElevation == 0f) { // 2. Take elevation settings mElevation = mAdapter.getStickyHeaderElevation(); } + if (mElevation > 0) { + // Needed to elevate the view + ViewCompat.setBackground(mStickyHolderLayout, mStickyHeaderViewHolder.getContentView().getBackground()); + } } private void translateHeader() { @@ -268,7 +268,7 @@ private void clearHeader() { if (mStickyHeaderViewHolder != null) { if (FlexibleAdapter.DEBUG) Log.d(TAG, "clearHeader"); resetHeader(mStickyHeaderViewHolder); - mStickyHolderLayout.setBackgroundColor(Color.TRANSPARENT); + mStickyHolderLayout.setAlpha(0); mStickyHolderLayout.animate().setListener(null); mStickyHeaderViewHolder = null; mHeaderPosition = RecyclerView.NO_POSITION; diff --git a/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java b/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java index 8a318e2e..09aae99b 100644 --- a/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java +++ b/flexible-adapter/src/main/java/eu/davidea/viewholders/ContentViewHolder.java @@ -15,7 +15,6 @@ */ package eu.davidea.viewholders; -import android.graphics.Color; import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.view.View; @@ -51,8 +50,11 @@ abstract class ContentViewHolder extends RecyclerView.ViewHolder { itemView.setLayoutParams(adapter.getRecyclerView().getLayoutManager() .generateLayoutParams(view.getLayoutParams())); ((FrameLayout) itemView).addView(view); //Add View after setLayoutParams - itemView.setBackgroundColor(Color.WHITE); - ViewCompat.setElevation(itemView, ViewCompat.getElevation(view)); + float elevation = ViewCompat.getElevation(view); + if (elevation > 0) { + ViewCompat.setBackground(itemView, view.getBackground()); + ViewCompat.setElevation(itemView, elevation); + } contentView = view; } } From 32acf5f461ee4c3071f4fe4197edc77d83b15d0b Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 14 Dec 2016 23:58:18 +0100 Subject: [PATCH 70/92] Resolves #248 Improved sticky header transparency on swapping --- .../main/res/layout/recycler_header_item.xml | 2 +- flexible-adapter/build.gradle | 2 +- .../flexibleadapter/AnimatorAdapter.java | 14 +---------- .../flexibleadapter/FlexibleAdapter.java | 7 ++++++ .../flexibleadapter/SelectableAdapter.java | 8 ++++++- .../helpers/StickyHeaderHelper.java | 24 +++++++++++++++---- 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/flexible-adapter-app/src/main/res/layout/recycler_header_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_header_item.xml index ddd12922..32350180 100644 --- a/flexible-adapter-app/src/main/res/layout/recycler_header_item.xml +++ b/flexible-adapter-app/src/main/res/layout/recycler_header_item.xml @@ -3,7 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="?android:attr/listPreferredItemHeightSmall" - android:background="#dd7e57c2" + android:background="#aa7e57c2" android:elevation="5dp"> 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, @@ -302,18 +302,6 @@ 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 */ /*--------------*/ 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 5d78aef9..f7f0ff8f 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -1866,6 +1866,13 @@ else if (elevation > 0)//Leave unaltered the default elevation 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 + 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 onLoadMore(position); 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 68a0f0fe..50966502 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java @@ -75,6 +75,12 @@ public abstract class SelectableAdapter extends RecyclerView.Adapter 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.

      @@ -544,7 +550,7 @@ public String onCreateBubbleText(int position) { */ @Override public void onFastScrollerStateChange(boolean scrolling) { - //nothing + isFastScroll = scrolling; } } \ No newline at end of file diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index a9b787d4..416919ae 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -118,6 +118,10 @@ private void initStickyHeadersHolder() { updateOrClearHeader(false); } + public int getStickyPosition() { + return mHeaderPosition; + } + private boolean hasStickyHeaderTranslated(int position) { RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(position); return vh != null && (vh.itemView.getX() < 0 || vh.itemView.getY() < 0); @@ -134,7 +138,7 @@ public void updateOrClearHeader(boolean updateHeaderContent) { clearHeaderWithAnimation(); return; } - int firstHeaderPosition = getHeaderPosition(RecyclerView.NO_POSITION); + int firstHeaderPosition = getStickyPosition(RecyclerView.NO_POSITION); if (firstHeaderPosition >= 0) { updateHeader(firstHeaderPosition, updateHeaderContent); } else { @@ -192,7 +196,7 @@ private void translateHeader() { final View nextChild = mRecyclerView.getChildAt(i); if (nextChild != null) { int adapterPos = mRecyclerView.getChildAdapterPosition(nextChild); - int nextHeaderPosition = getHeaderPosition(adapterPos); + int nextHeaderPosition = getStickyPosition(adapterPos); if (mHeaderPosition != nextHeaderPosition) { if (Utils.getOrientation(mRecyclerView.getLayoutManager()) == OrientationHelper.HORIZONTAL) { if (nextChild.getLeft() > 0) { @@ -223,7 +227,7 @@ private void translateHeader() { // Apply translation (pushed up by another header) mStickyHolderLayout.setTranslationX(headerOffsetX); mStickyHolderLayout.setTranslationY(headerOffsetY); -// Log.v(TAG, "TranslationX=" + headerOffsetX + " TranslationY=" + headerOffsetY); + //Log.v(TAG, "TranslationX=" + headerOffsetX + " TranslationY=" + headerOffsetY); } private void swapHeader(FlexibleViewHolder newHeader) { @@ -244,6 +248,8 @@ private void ensureHeaderParent() { // WRAP_CONTENT has been set for the Header View mStickyHeaderViewHolder.itemView.getLayoutParams().width = view.getMeasuredWidth(); mStickyHeaderViewHolder.itemView.getLayoutParams().height = view.getMeasuredHeight(); + // Ensure the itemView is hidden to avoid double background + mStickyHeaderViewHolder.itemView.setVisibility(View.INVISIBLE); // #139 - Copy xml params instead of Measured params ViewGroup.LayoutParams params = mStickyHolderLayout.getLayoutParams(); params.width = view.getLayoutParams().width; @@ -253,7 +259,16 @@ private void ensureHeaderParent() { configureLayoutElevation(); } + private void restoreHeaderItemVisibility(int child) { + // Restore the visibility to first header + if (mRecyclerView != null) { + View oldHeader = mRecyclerView.getChildAt(child); + if (oldHeader != null) oldHeader.setVisibility(View.VISIBLE); + } + } private void resetHeader(FlexibleViewHolder header) { + restoreHeaderItemVisibility(1); + // Clean the header container final View view = header.getContentView(); removeViewFromParent(view); // Reset translation on removed header @@ -271,6 +286,7 @@ private void clearHeader() { mStickyHolderLayout.setAlpha(0); mStickyHolderLayout.animate().setListener(null); mStickyHeaderViewHolder = null; + restoreHeaderItemVisibility(0); mHeaderPosition = RecyclerView.NO_POSITION; onStickyHeaderChange(mHeaderPosition); } @@ -311,7 +327,7 @@ private static void removeViewFromParent(final View view) { } @SuppressWarnings("unchecked") - private int getHeaderPosition(int adapterPosHere) { + private int getStickyPosition(int adapterPosHere) { if (adapterPosHere == RecyclerView.NO_POSITION) { // Fix to display correct sticky header (especially after the searchText is cleared out) if (mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) { From 1ca6f3b47ea5fc1d619bf269a0e397cb60486412 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Thu, 15 Dec 2016 00:36:36 +0100 Subject: [PATCH 71/92] Resolves #248 Small improvements for sticky header animation on swapping --- .../flexibleadapter/FlexibleAdapter.java | 364 +++++++++--------- .../helpers/StickyHeaderHelper.java | 29 +- 2 files changed, 201 insertions(+), 192 deletions(-) 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 f7f0ff8f..6ecefbca 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -242,10 +242,10 @@ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners, bool mRestoreList = new ArrayList<>(); mOrphanHeaders = new ArrayList<>(); - //Create listeners instances + // 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()); } @@ -397,24 +397,24 @@ public boolean isSelectable(int position) { @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) mSelectedLevel = parent.getExpansionLevel(); super.toggleSelection(position); } 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 + // Allow selection of Child of same level and if no Parent has been previously selected childSelected = true; 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) { mSelectedLevel = -1; parentSelected = childSelected = false; @@ -439,9 +439,9 @@ public void toggleSelection(@IntRange(from = 0) int position) { @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 } } @@ -703,10 +703,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()) { @@ -715,8 +715,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) { @@ -727,7 +727,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); @@ -1176,9 +1176,9 @@ public IHeader getHeaderOf(@NonNull T item) { * @since 5.0.0-b6 */ 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; @@ -1587,14 +1587,14 @@ private void showAllHeadersWithReset(boolean init) { * @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); - //Insert header, but skip notifyItemInserted when init=true! + // Insert header, but skip notifyItemInserted when init=true! performInsert(position, Collections.singletonList((T) header), !init); return true; } @@ -1614,7 +1614,7 @@ public void hideAllHeaders() { @Override public void run() { multiRange = true; - //Hide linked headers between Scrollable Headers and Footers + // 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); @@ -1627,7 +1627,7 @@ public void run() { if (areHeadersSticky()) { mStickyHeaderHelper.clearHeaderWithAnimation(); } - //setStickyHeaders(false); + // setStickyHeaders(false); multiRange = false; } }); @@ -1640,9 +1640,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); } @@ -1650,7 +1650,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; @@ -1675,7 +1675,7 @@ 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); } @@ -1685,7 +1685,7 @@ private boolean linkHeaderTo(@NonNull T item, @NonNull IHeader header, @Nullable 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); @@ -1715,7 +1715,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); @@ -1727,7 +1727,7 @@ private IHeader unlinkHeaderFrom(@NonNull T item, @Nullable Object payload) { @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) @@ -1746,11 +1746,11 @@ private boolean isHeaderShared(IHeader header, int positionStart, int itemCount) int firstElementWithHeader = getGlobalPositionOf(header) + 1; 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; } @@ -1775,7 +1775,7 @@ private boolean isHeaderShared(IHeader header, int positionStart, int itemCount) @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(); @@ -1800,7 +1800,7 @@ public int getItemViewType(int position) { public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { T item = getViewTypeInstance(viewType); if (item == null || !autoMap) { - //If everything has been set properly, this should never happen ;-) + // 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."); } @@ -1846,13 +1846,13 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List " itemId=" + holder.getItemId() + " layoutPosition=" + holder.getLayoutPosition()); } if (!autoMap) { - //If everything has been set properly, this should never happen ;-) + // 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."); } - //When user scrolls, this line binds the correct selection status + // When user scrolls, this line binds the correct selection status holder.itemView.setActivated(isSelected(position)); - //Bind the correct view elevation + // Bind the correct view elevation if (holder instanceof FlexibleViewHolder) { FlexibleViewHolder flexHolder = (FlexibleViewHolder) holder; float elevation = flexHolder.getActivationElevation(); @@ -1861,7 +1861,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List else if (elevation > 0)//Leave unaltered the default elevation ViewCompat.setElevation(holder.itemView, 0); } - //Bind the item + // Bind the item T item = getItem(position); if (item != null) { holder.itemView.setEnabled(item.isEnabled()); @@ -1874,9 +1874,9 @@ else if (elevation > 0)//Leave unaltered the default elevation holder.itemView.setVisibility(View.INVISIBLE); } } - //Endless Scroll + // Endless Scroll onLoadMore(position); - //Scroll Animation + // Scroll Animation animateView(holder, position); } @@ -2025,7 +2025,7 @@ 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 = Utils.getSpanCount(mRecyclerView.getLayoutManager()); thresholdItems = thresholdItems * spanCount; @@ -2494,13 +2494,13 @@ private int expand(int position, boolean expandAll, boolean init) { if (init || !expandable.isExpanded() && (!parentSelected || expandable.getExpansionLevel() <= mSelectedLevel)) { - //Collapse others expandable if configured so Skip when expanding all is requested - //Fetch again the new position after collapsing all!! + // 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); @@ -2508,15 +2508,15 @@ 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) { @@ -2524,7 +2524,7 @@ private int expand(int position, boolean expandAll, boolean init) { } } - //Expandable as a Scrollable Header/Footer + // Expandable as a Scrollable Header/Footer if (!expandSHF(mScrollableHeaders, expandable)) expandSHF(mScrollableFooters, expandable); @@ -2571,7 +2571,7 @@ public int expandAll() { */ public int expandAll(int level) { int expanded = 0; - //More efficient if we expand from First expandable position + // 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); @@ -2601,7 +2601,7 @@ 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; @@ -2614,25 +2614,25 @@ 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) + // 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 + // Expandable as a Scrollable Header/Footer if (!collapseSHF(mScrollableHeaders, expandable)) collapseSHF(mScrollableFooters, expandable); @@ -2683,7 +2683,7 @@ public int collapseAll() { * @since 5.0.0-b6 */ public int collapseAll(int level) { - //Use hashSet to increase performance while removing subItems + // Use hashSet to increase performance while removing subItems mHashItems = new LinkedHashSet<>(mItems); int collapsed = recursiveCollapse(0, mItems, level); mItems = new ArrayList<>(mHashItems); @@ -2825,17 +2825,17 @@ public boolean addItems(@IntRange(from = 0) int position, @NonNull List items Log.w(TAG, "addItems Position is negative! adding items to the end"); position = initialCount; } - //Insert the item properly + // 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(getMainItemCount()); return true; @@ -2849,7 +2849,7 @@ private void performInsert(int position, List items, boolean notify) { mItems.addAll(items); position = itemCount; } - //Notify range addition + // Notify range addition if (notify) { if (DEBUG) Log.d(TAG, "addItems on position=" + position + " itemCount=" + items.size()); @@ -2894,7 +2894,7 @@ 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 + // Build a new list with 1 item to chain the methods of addSubItems return addSubItems(parentPosition, subPosition, Collections.singletonList(item), expandParent, payload); } @@ -2983,16 +2983,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 add sub items. + // 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; } @@ -3230,7 +3230,7 @@ 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); @@ -3271,9 +3271,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) { @@ -3282,7 +3282,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; @@ -3291,17 +3291,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 } @@ -3399,7 +3399,7 @@ public void removeRange(@IntRange(from = 0) int 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); } @@ -3418,11 +3418,11 @@ 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 item is a Header, remove linkage from ALL Sectionable items if exist if (unlinkOnRemoveHeader) { List sectionableList = getSectionItems(header); for (ISectionable sectionable : sectionableList) { @@ -3432,19 +3432,19 @@ public void removeRange(@IntRange(from = 0) int positionStart, } } } - //Remove item from internal list + // Remove item from internal list mItems.remove(positionStart); removeSelection(position); } - //Notify range removal + // Notify range removal notifyItemRangeRemoved(positionStart, itemCount); - //Notify the Parent about the change if requested + // 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); @@ -3459,7 +3459,7 @@ 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(getMainItemCount()); } @@ -3572,53 +3572,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); @@ -3627,7 +3627,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; @@ -3642,7 +3642,7 @@ 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(getMainItemCount()); @@ -3680,7 +3680,7 @@ public void startUndoTimer(OnDeleteCompleteListener listener) { * @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); } @@ -3759,13 +3759,13 @@ public final List getDeletedChildren(IExpandable expandable) { */ @NonNull public final List getCurrentChildren(@NonNull IExpandable expandable) { - //Check item and subItems existence + // 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)); } @@ -3918,13 +3918,13 @@ private synchronized void filterItemsAsync(@NonNull List unfilteredItems) { 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() && 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()) @@ -3935,7 +3935,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 { @@ -3949,27 +3949,27 @@ 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; animateDiff(filteredItems, Payload.FILTER); @@ -3989,29 +3989,29 @@ 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()); } @@ -4044,7 +4044,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) { @@ -4061,21 +4061,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); @@ -4210,21 +4210,21 @@ private synchronized void animateTo(@Nullable List newItems, Payload payloadC * @since 5.0.0-b1 */ private void applyAndAnimateRemovals(List from, List newItems) { - //This avoids the call indexOf() later on: newItems.get(newItems.indexOf(item))); + // 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 + // 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 + // Save the index of this new item if (mHashItems.contains(item)) existingItems.put(item, i); } } - //Using Hash for performance + // Using Hash for performance mHashItems = new HashSet<>(newItems); int out = 0, mod = 0; for (int i = from.size() - 1; i >= 0; i--) { @@ -4255,16 +4255,16 @@ private void applyAndAnimateRemovals(List from, List newItems) { * @since 5.0.0-b1 */ private void applyAndAnimateAdditions(List from, List newItems) { - //Using Hash for performance + // 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 (!mHashItems.contains(item)) { - //if (DEBUG) Log.v(TAG, "calculateAdditions add position=" + i + " item=" + item + " searchText=" + mSearchText); + // 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); mNotifications.add(new Notification(from.size(), Notification.ADD)); } else { @@ -4291,7 +4291,7 @@ 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); @@ -4527,25 +4527,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); 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); } @@ -4568,12 +4568,12 @@ public void swapItems(List list, int fromPosition, int toPosition) { toPosition + " [selected? " + isSelected(toPosition) + "]"); } - //Collapse expandable before swapping (otherwise items are mixed badly) + // 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)); @@ -4589,24 +4589,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) { @@ -4614,27 +4614,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) { @@ -4698,7 +4698,7 @@ 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); } @@ -4739,7 +4739,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; @@ -4769,15 +4769,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; } @@ -4797,7 +4797,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); } } @@ -4828,7 +4828,7 @@ private void performScroll(final int position) { } 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()); @@ -4861,7 +4861,7 @@ private void adjustSelected(int startPosition, int itemCount) { boolean adjusted = false; String diff = ""; if (itemCount > 0) { - //Reverse sorting is necessary because using Set removes duplicates + // Reverse sorting is necessary because using Set removes duplicates // during adjusting, so we scan backward. Collections.sort(selectedPositions, new Comparator() { @Override @@ -4896,19 +4896,19 @@ public int compare(Integer lhs, Integer rhs) { */ 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 + // 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.mSelectedLevel); - //Current filter. Old text is not saved otherwise animateTo() cannot be called + // 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()); } @@ -4922,7 +4922,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(); @@ -4933,17 +4933,17 @@ public void onRestoreInstanceState(Bundle savedInstanceState) { if (savedInstanceState.getBoolean(EXTRA_STICKY) && !areHeadersSticky()) { setStickyHeaders(true); } - //Restore selection state + // Restore selection state super.onRestoreInstanceState(savedInstanceState); if (mScrollableHeaders.size() > 0) { - //We need to restore the added item positions if headers were added early + // We need to restore the added item positions if headers were added early adjustSelected(0, mScrollableHeaders.size()); } - //Restore selection coherence + // Restore selection coherence this.parentSelected = savedInstanceState.getBoolean(EXTRA_PARENT); this.childSelected = savedInstanceState.getBoolean(EXTRA_CHILD); this.mSelectedLevel = savedInstanceState.getInt(EXTRA_LEVEL); - //Current filter (old text must not be saved) + // Current filter (old text must not be saved) this.mSearchText = savedInstanceState.getString(EXTRA_SEARCH); } } @@ -5137,8 +5137,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; } @@ -5194,7 +5194,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; @@ -5209,7 +5209,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; @@ -5298,12 +5298,12 @@ protected void onPostExecute(Void result) { //Execute post data switch (what) { case UPDATE: - //Notify all the changes + // Notify all the changes executeNotifications(Payload.CHANGE); postUpdate(false); break; case FILTER: - //Notify all the changes + // Notify all the changes executeNotifications(Payload.FILTER); postFilter(); break; @@ -5318,21 +5318,21 @@ protected void onPostExecute(Void result) { * {@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, "updateDataSet with notifyDataSetChanged!"); notifyDataSetChanged(); } // Perform user code onPostUpdate(); - //Update empty view + // Update empty view if (mUpdateListener != null) { mUpdateListener.onUpdateEmptyView(getMainItemCount()); } @@ -5345,17 +5345,17 @@ private void postUpdate(boolean init) { * @see #updateDataSet(List, boolean) */ protected void onPostUpdate() { - //Dedicated for user implementation + // Dedicated for user implementation } private void postFilter() { - //Restore headers if necessary + // Restore headers if necessary if (headersShown && !hasSearchText()) { showAllHeadersWithReset(false); } - //Perform user code + // Perform user code onPostFilter(); - //Call listener to update EmptyView, assuming the filter always made a change + // Call listener to update EmptyView, assuming the filter always made a change if (mUpdateListener != null) mUpdateListener.onUpdateEmptyView(getMainItemCount()); } @@ -5367,7 +5367,7 @@ private void postFilter() { * @see #filterItems(List) */ protected void onPostFilter() { - //Dedicated for user implementation + // Dedicated for user implementation } /** diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index 416919ae..4bb2f44a 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -96,7 +96,7 @@ public void detachFromRecyclerView() { // } private static ViewGroup getParent(View view) { - return (ViewGroup)view.getParent(); + return (ViewGroup) view.getParent(); } private void initStickyHeadersHolder() { @@ -203,7 +203,6 @@ private void translateHeader() { int headerWidth = mStickyHolderLayout.getMeasuredWidth(); int nextHeaderOffsetX = nextChild.getLeft() - headerWidth; headerOffsetX = Math.min(nextHeaderOffsetX, 0); - // TODO: AlphaListener = Math.abs((float)nextChild.getLeft()) / headerWidth); // Early remove the elevation/shadow to match with the next view if (nextHeaderOffsetX < 5) elevation = 0f; if (headerOffsetX < 0) break; @@ -213,7 +212,6 @@ private void translateHeader() { int headerHeight = mStickyHolderLayout.getMeasuredHeight(); int nextHeaderOffsetY = nextChild.getTop() - headerHeight; headerOffsetY = Math.min(nextHeaderOffsetY, 0); - // TODO: AlphaListener = Math.abs((float)nextChild.getTop()) / headerHeight); // Early remove the elevation/shadow to match with the next view if (nextHeaderOffsetY < 5) elevation = 0f; if (headerOffsetY < 0) break; @@ -259,15 +257,25 @@ private void ensureHeaderParent() { configureLayoutElevation(); } - private void restoreHeaderItemVisibility(int child) { - // Restore the visibility to first header - if (mRecyclerView != null) { - View oldHeader = mRecyclerView.getChildAt(child); - if (oldHeader != null) oldHeader.setVisibility(View.VISIBLE); + /** + * On swing and on fast scroll some header items might still be invisible. We need + * to identify them and restore visibility. + */ + @SuppressWarnings("unchecked") + private void restoreHeaderItemVisibility() { + if (mRecyclerView == null) return; + // Restore every header item visibility + for (int i = 0; i < mRecyclerView.getChildCount(); i++) { + View oldHeader = mRecyclerView.getChildAt(i); + int headerPos = mRecyclerView.getChildAdapterPosition(oldHeader); + if (mAdapter.isHeader(mAdapter.getItem(headerPos))) { + oldHeader.setVisibility(View.VISIBLE); + } } } + private void resetHeader(FlexibleViewHolder header) { - restoreHeaderItemVisibility(1); + restoreHeaderItemVisibility(); // Clean the header container final View view = header.getContentView(); removeViewFromParent(view); @@ -284,9 +292,10 @@ private void clearHeader() { if (FlexibleAdapter.DEBUG) Log.d(TAG, "clearHeader"); resetHeader(mStickyHeaderViewHolder); mStickyHolderLayout.setAlpha(0); + mStickyHolderLayout.animate().cancel(); mStickyHolderLayout.animate().setListener(null); mStickyHeaderViewHolder = null; - restoreHeaderItemVisibility(0); + restoreHeaderItemVisibility(); mHeaderPosition = RecyclerView.NO_POSITION; onStickyHeaderChange(mHeaderPosition); } From 22a428f4995538531be70ed40eec402a67c12b2a Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Thu, 15 Dec 2016 23:19:12 +0100 Subject: [PATCH 72/92] Upgraded buildTools and supportLib --- build.gradle | 4 ++-- flexible-adapter/build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index b87b5043..d066838f 100644 --- a/build.gradle +++ b/build.gradle @@ -24,8 +24,8 @@ ext { //Support and Build tools version minSdk = 14 targetSdk = 25 - buildTools = "25.0.1" - supportLib = "25.0.1" + buildTools = "25.0.2" + supportLib = "25.1.0" //Support Libraries dependencies supportDependencies = [ diff --git a/flexible-adapter/build.gradle b/flexible-adapter/build.gradle index d3fef7ca..c5f69fb7 100644 --- a/flexible-adapter/build.gradle +++ b/flexible-adapter/build.gradle @@ -25,6 +25,6 @@ dependencies { } //apply from: '../maven-install.gradle' -apply from: '../maven-publish.gradle' +//apply from: '../maven-publish.gradle' //apply from: '../jfrog-bintray-publish.gradle' //apply from: '../jfrog-artifactory-publish.gradle' From 8e961701f6d8b340bf8976d8b36c16c74340b90e Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 18 Dec 2016 17:33:41 +0100 Subject: [PATCH 73/92] Imported the last ActivatedPosition in the ActionModeHelper, which has a new method: getActivatedPosition() --- .../samples/flexibleadapter/MainActivity.java | 217 ++++++++---------- .../src/main/res/values-v19/styles.xml | 2 +- .../helpers/ActionModeHelper.java | 45 ++-- 3 files changed, 123 insertions(+), 141 deletions(-) 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 07ed3a8a..e3233238 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 @@ -122,11 +122,6 @@ public class MainActivity extends AppCompatActivity implements * Bundle key representing the Active Fragment */ private static final String STATE_ACTIVE_FRAGMENT = "active_fragment"; - /** - * 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"; /** * FAB @@ -147,11 +142,6 @@ public class MainActivity extends AppCompatActivity implements private AbstractFragment mFragment; private SearchView mSearchView; - /** - * The current activated item position. - */ - private int mActivatedPosition = RecyclerView.NO_POSITION; - private final Handler mRefreshHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { public boolean handleMessage(Message message) { switch (message.what) { @@ -187,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(); } @@ -210,14 +200,11 @@ 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); - //Previously serialized activated item position - if (savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) - setSelection(savedInstanceState.getInt(STATE_ACTIVATED_POSITION)); } } @@ -258,7 +245,7 @@ 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! @@ -268,7 +255,7 @@ private void initializeSwipeToRefresh() { 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); @@ -284,7 +271,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); } @@ -298,7 +285,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), @@ -314,7 +301,7 @@ public void onClick(View v) { mFragment.performFabAction(); } }); - //No Fab on 1st fragment + // No Fab on 1st fragment hideFabSilently(); } @@ -340,7 +327,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); @@ -369,7 +356,7 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { Intent intent = new Intent(this, ViewPagerActivity.class); ActivityOptionsCompat activityOptionsCompat = ActivityOptionsCompat.makeBasic(); ActivityCompat.startActivity(this, intent, activityOptionsCompat.toBundle()); - //Close drawer + // Close drawer mRecyclerView.post(new Runnable() { @Override public void run() { @@ -391,18 +378,18 @@ public void run() { } // 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() { @@ -447,7 +434,7 @@ private void showFab() { @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); @@ -486,11 +473,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; } @@ -521,39 +508,39 @@ 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.isAnimationOnReverseScrollingEnabled()); } - //DiffUtil? + // DiffUtil? MenuItem diffUtilItem = menu.findItem(R.id.action_diff_util); if (diffUtilItem != null) { diffUtilItem.setChecked(DatabaseConfiguration.animateWithDiffUtil); @@ -676,27 +663,6 @@ public void onTitleModified(int position, String newTitle) { mAdapter.updateItem(position, abstractItem, null); } - /* LAST SELECTED ITEM */ - - private void setActivatedPosition(int position) { - Log.d(TAG, "ItemList New mActivatedPosition=" + position); - mActivatedPosition = position; - } - - //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); - } - } - /* FLEXIBLE ADAPTER LISTENERS IMPLEMENTATION */ @Override @@ -709,12 +675,13 @@ 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) { - if (position != mActivatedPosition) setActivatedPosition(position); - 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 + // Notify the active callbacks or implement a custom action onClick if (!(flexibleItem instanceof ExpandableItem) && flexibleItem instanceof SimpleItem || flexibleItem instanceof SubItem) { //TODO FOR YOU: call your custom Action on item click @@ -754,32 +721,32 @@ 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 + // Example of UNDO color int actionTextColor = Color.TRANSPARENT; if (Utils.hasMarshmallow()) { actionTextColor = getResources().getColor(R.color.material_color_orange_500, this.getTheme()); @@ -788,13 +755,13 @@ public void onItemSwipe(final int position, int direction) { } 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; } }) @@ -806,11 +773,11 @@ public boolean onPreAction() { } else if (direction == ItemTouchHelper.RIGHT) { message.append(getString(R.string.action_deleted)); 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() { - //Handle ActionMode title + // Handle ActionMode title if (mAdapter.getSelectedItemCount() == 0) mActionModeHelper.destroyActionModeIfCan(); else @@ -854,7 +821,7 @@ public void onUpdateEmptyView(int size) { @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(); @@ -868,11 +835,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 + // Enable SwipeRefresh mRefreshHandler.sendEmptyMessage(0); - //Check also selection restoration + // Check also selection restoration if (mAdapter.isRestoreWithSelection()) { mActionModeHelper.restoreSelection(this); } @@ -881,12 +848,12 @@ public void onUndoConfirmed(int action) { @Override public void onDeleteConfirmed(int action) { - //Enable SwipeRefresh + // Enable SwipeRefresh mRefreshHandler.sendEmptyMessage(0); - //Removing items from Database. Example: + // 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; @@ -900,15 +867,15 @@ 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) + // SimpleItem or ExpandableItem(extends SimpleItem) DatabaseService.getInstance().removeItem(adapterItem); Log.d(TAG, "Confirm removed " + adapterItem); } @@ -940,11 +907,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()) { @@ -953,26 +920,26 @@ 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 + // Disable SwipeRefresh mRefreshHandler.sendEmptyMessage(1); mRefreshHandler.sendEmptyMessageDelayed(0, 20000); - //Finish the action mode + // Finish the action mode mActionModeHelper.destroyActionModeIfCan(); } }) @@ -980,26 +947,26 @@ public void onPostAction() { findViewById(R.id.main_view), message, getString(R.string.undo), 20000); - //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 @@ -1011,25 +978,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; } } @@ -1048,25 +1015,25 @@ public void onDestroyActionMode(ActionMode mode) { @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(); } @@ -1086,7 +1053,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/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/src/main/java/eu/davidea/flexibleadapter/helpers/ActionModeHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ActionModeHelper.java index df11e797..d7e5760e 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ActionModeHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ActionModeHelper.java @@ -43,6 +43,7 @@ public class ActionModeHelper implements ActionMode.Callback { private int defaultMode = SelectableAdapter.MODE_IDLE; @MenuRes private int mCabMenu; + private int mActivatedPosition; private FlexibleAdapter mAdapter; private ActionMode.Callback mCallback; protected ActionMode mActionMode; @@ -58,6 +59,7 @@ public class ActionModeHelper implements ActionMode.Callback { public ActionModeHelper(@NonNull FlexibleAdapter adapter, @MenuRes int cabMenu) { this.mAdapter = adapter; this.mCabMenu = cabMenu; + this.mActivatedPosition = RecyclerView.NO_POSITION; } /** @@ -99,6 +101,16 @@ public ActionMode getActionMode() { return mActionMode; } + /** + * Gets the last activated position, especially useful in {@code MODE_SINGLE}. + * + * @return the last activated position, -1 if no item is selected + * @since 5.0.0-rc1 + */ + public int getActivatedPosition() { + return mActivatedPosition; + } + /** * Implements the basic behavior of a CAB and multi select behavior. * @@ -125,11 +137,11 @@ public boolean onClick(int position) { */ @NonNull public ActionMode onLongClick(AppCompatActivity activity, int position) { - //Activate ActionMode + // Activate ActionMode if (mActionMode == null) { mActionMode = activity.startSupportActionMode(this); } - //we have to select this on our own as we will consume the event + // We have to select this on our own as we will consume the event toggleSelection(position); return mActionMode; } @@ -143,11 +155,13 @@ public ActionMode onLongClick(AppCompatActivity activity, int position) { * @since 5.0.0-b6 */ public void toggleSelection(int position) { - if (position >= 0 && (mAdapter.getMode() == SelectableAdapter.MODE_SINGLE || + if (position >= 0 && ( + (mAdapter.getMode() == SelectableAdapter.MODE_SINGLE && position != mActivatedPosition) || mAdapter.getMode() == SelectableAdapter.MODE_MULTI)) { + mActivatedPosition = position; mAdapter.toggleSelection(position); } - //If MODE_SINGLE is active then ActionMode can be null + // If MODE_SINGLE is active then ActionMode can be null if (mActionMode == null) return; int count = mAdapter.getSelectedItemCount(); @@ -172,11 +186,11 @@ public void updateContextTitle(int count) { } /** - * Helper method to restart the action mode after a restoration of deleted items. The - * ActionMode will be activated only if {@link FlexibleAdapter#getSelectedItemCount()} - * has selection. - *

      To be called in the onUndo method after the restoration is done or in the - * onRestoreInstanceState.

      + * Helper method to restart the action mode after a restoration of deleted items and after + * screen rotation. The ActionMode will be activated only if + * {@link FlexibleAdapter#getSelectedItemCount()} has selections. + *

      To be called in the {@code onUndo} method after the restoration is done or at the end + * of {@code onRestoreInstanceState}.

      * * @param activity the current Activity * @since 5.0.0-b6 @@ -191,12 +205,12 @@ public void restoreSelection(AppCompatActivity activity) { @CallSuper @Override public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { - //Inflate the Context Menu + // Inflate the Context Menu actionMode.getMenuInflater().inflate(mCabMenu, menu); if (SelectableAdapter.DEBUG) Log.i(TAG, "ActionMode is active!"); - //Activate the ActionMode Multi + // Activate the ActionMode Multi mAdapter.setMode(SelectableAdapter.MODE_MULTI); - //Notify the provided callback + // Notify the provided callback return mCallback == null || mCallback.onCreateActionMode(actionMode, menu); } @@ -214,7 +228,7 @@ public boolean onActionItemClicked(ActionMode actionMode, MenuItem item) { consumed = mCallback.onActionItemClicked(actionMode, item); } if (!consumed) { - //Finish the actionMode + // Finish the actionMode actionMode.finish(); } return consumed; @@ -230,11 +244,12 @@ public boolean onActionItemClicked(ActionMode actionMode, MenuItem item) { public void onDestroyActionMode(ActionMode actionMode) { if (SelectableAdapter.DEBUG) Log.i(TAG, "ActionMode is about to be destroyed! New mode will be " + defaultMode); - //Change mode and deselect everything + // Change mode and deselect everything mAdapter.setMode(defaultMode); + mActivatedPosition = RecyclerView.NO_POSITION; mAdapter.clearSelection(); mActionMode = null; - //Notify the provided callback + // Notify the provided callback if (mCallback != null) { mCallback.onDestroyActionMode(actionMode); } From f477436cbf75e4740283641d202e29dc8a258903 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 18 Dec 2016 23:16:39 +0100 Subject: [PATCH 74/92] Refactored some methods of DrawableUtils --- .../flexibleadapter/items/SimpleItem.java | 26 +++- .../flexibleadapter/items/StaggeredItem.java | 6 +- .../flexibleadapter/utils/DrawableUtils.java | 123 ++++++++++++------ .../davidea/flexibleadapter/utils/Utils.java | 16 +++ 4 files changed, 124 insertions(+), 47 deletions(-) diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java index f1c3f428..ab2c7cb8 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java @@ -3,9 +3,11 @@ 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; @@ -82,6 +84,8 @@ public ParentViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater @Override @SuppressWarnings({"unchecked"}) public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holder, int position, List payloads) { + Context context = holder.itemView.getContext(); + // Subtitle if (adapter.isExpandable(this)) { setSubtitle(adapter.getCurrentChildren((IExpandable) this).size() + " subItems" @@ -89,8 +93,12 @@ public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holde + (getUpdates() > 0 ? " - u" + getUpdates() : "")); } - DrawableUtils.setBackground(holder.itemView, DrawableUtils.getSelectableBackgroundCompat( - Color.LTGRAY, Color.WHITE, Color.LTGRAY)); + if (payloads.size() == 0) { + Drawable drawable = DrawableUtils.getSelectableBackgroundCompat( + Color.WHITE, Color.parseColor("#dddddd"), DrawableUtils.getColorControlHighlight(context)); + DrawableUtils.setBackgroundCompat(holder.itemView, drawable); + DrawableUtils.setBackgroundCompat(holder.frontView, drawable); + } if (adapter.isExpandable(this) && payloads.size() > 0) { Log.d(this.getClass().getSimpleName(), "ExpandableItem Payload " + payloads); @@ -141,8 +149,10 @@ static final class ParentViewHolder extends ExpandableViewHolder { ImageView mHandleView; Context mContext; View frontView; - private View rearLeftView; - private View rearRightView; + View rearLeftView; + View rearRightView; + + public boolean swiped = false; ParentViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); @@ -191,7 +201,7 @@ public boolean onLongClick(View view) { } @Override - protected void toggleActivation() { + public void toggleActivation() { super.toggleActivation(); //Here we use a custom Animation inside the ItemView mFlipView.flip(mAdapter.isSelected(getAdapterPosition())); @@ -243,6 +253,12 @@ 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 diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java index 7671654a..281d22eb 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java @@ -116,15 +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/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java index a861f030..d3235c96 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java @@ -25,6 +25,7 @@ import android.graphics.drawable.shapes.RoundRectShape; import android.support.annotation.ColorInt; import android.support.annotation.DrawableRes; +import android.support.v4.view.ViewCompat; import android.util.TypedValue; import android.view.View; @@ -36,6 +37,7 @@ * @author Davide Steduto * @since 14/06/2016 Created */ +@SuppressWarnings("deprecation") public final class DrawableUtils { /** @@ -44,15 +46,11 @@ public final class DrawableUtils { * @param view the view to apply the drawable * @param drawable drawable object * @since 5.0.0-b7 + * @deprecated Use {@link #setBackgroundCompat(View, Drawable)} instead. */ - //TODO: Rename to setBackgroundCompat - @SuppressWarnings("deprecation") + @Deprecated public static void setBackground(View view, Drawable drawable) { - if (Utils.hasJellyBean()) { - view.setBackground(drawable); - } else { - view.setBackgroundDrawable(drawable); - } + setBackgroundCompat(view, drawable); } /** @@ -61,10 +59,33 @@ public static void setBackground(View view, Drawable drawable) { * @param view the view to apply the drawable * @param drawableRes drawable resource id * @since 5.0.0-b7 + * @deprecated Use {@link #setBackgroundCompat(View, int)} instead. */ - //TODO: Rename to setBackgroundCompat + @Deprecated public static void setBackground(View view, @DrawableRes int drawableRes) { - setBackground(view, getDrawableCompat(view.getContext(), drawableRes)); + setBackgroundCompat(view, getDrawableCompat(view.getContext(), drawableRes)); + } + + /** + * Helper method to set the background depending on the android version. + * + * @param view the view to apply the drawable + * @param drawable drawable object + * @since 5.0.0-rc1 + */ + public static void setBackgroundCompat(View view, Drawable drawable) { + ViewCompat.setBackground(view, drawable); + } + + /** + * Helper method to set the background depending on the android version + * + * @param view the view to apply the drawable + * @param drawableRes drawable resource id + * @since 5.0.0-rc1 + */ + public static void setBackgroundCompat(View view, @DrawableRes int drawableRes) { + setBackgroundCompat(view, getDrawableCompat(view.getContext(), drawableRes)); } /** @@ -75,7 +96,6 @@ public static void setBackground(View view, @DrawableRes int drawableRes) { * @return the drawable object * @since 5.0.0-b7 */ - @SuppressWarnings("deprecation") public static Drawable getDrawableCompat(Context context, @DrawableRes int drawableRes) { try { if (Utils.hasLollipop()) { @@ -89,63 +109,81 @@ public static Drawable getDrawableCompat(Context context, @DrawableRes int drawa } /** - * Helper to get the default Selectable Background. - * Returns the resourceId of the {@code R.attr.selectableItemBackground} attribute in your style. + * Helper to get the default Selectable Background. Returns the resourceId of the + * {@code R.attr.selectableItemBackground} attribute of the overridden style. * * @param context the context * @return Default selectable background resId * @since 5.0.0-b7 + * @deprecated Use {@link #getSelectableItemBackground(Context)} instead. */ - //TODO: Rename to getDefaultSelectableBackground ? + @Deprecated public static int getSelectableBackground(Context context) { TypedValue outValue = new TypedValue(); - //it is important here to not use the android.R because this wouldn't add the latest drawable + // It's important to not use the android.R because this wouldn't add the overridden drawable context.getTheme().resolveAttribute(R.attr.selectableItemBackground, outValue, true); return outValue.resourceId; } /** - * Helper to get the system default Color Control Highlight. - * Returns the resourceId of the {@code R.attr.colorControlHighlight} attribute in your style. + * Helper to get the default selectableItemBackground drawable of the + * {@code R.attr.selectableItemBackground} attribute of the overridden style. * * @param context the context - * @return Default Color Control Highlight resId - * @since 5.0.0-b7 + * @return Default selectable item background drawable + * @since 5.0.0-rc1 + */ + public static Drawable getSelectableItemBackground(Context context) { + TypedValue outValue = new TypedValue(); + // It's important to not use the android.R because this wouldn't add the overridden drawable + context.getTheme().resolveAttribute(R.attr.selectableItemBackground, outValue, true); + return getDrawableCompat(context, outValue.resourceId); + } + + /** + * Helper to get the system default Color Control Highlight. Returns the color of the + * {@code R.attr.colorControlHighlight} attribute in the overridden style. + * + * @param context the context + * @return Default Color Control Highlight + * @since 5.0.0-b7 Created, returns the resourceId + *
      5.0.0-rc1 Now returns the real color (not the resourceId) */ + @ColorInt public static int getColorControlHighlight(Context context) { TypedValue outValue = new TypedValue(); - //it is important here to not use the android.R because this wouldn't add the latest drawable + // It's important to not use the android.R because this wouldn't add the overridden drawable context.getTheme().resolveAttribute(R.attr.colorControlHighlight, outValue, true); - return outValue.resourceId; + if (Utils.hasMarshmallow()) return context.getColor(outValue.resourceId); + else return context.getResources().getColor(outValue.resourceId); } /** * Helper to get a custom selectable background with Ripple if device has at least Lollipop. * - * @param rippleColor the color of the ripple * @param normalColor the color in normal state * @param pressedColor the pressed color + * @param rippleColor the color of the ripple * @return the RippleDrawable with StateListDrawable if at least Lollipop, the normal * StateListDrawable otherwise - * @since 5.0.0-b7 + * @since 5.0.0-b7 Created + *
      5.0.0-rc1 RippleColor becomes the 3rd parameter */ - //TODO: Deprecate? change the method name and put the rippleColor to the end - public static Drawable getSelectableBackgroundCompat(@ColorInt int rippleColor, - @ColorInt int normalColor, - @ColorInt int pressedColor) { + public static Drawable getSelectableBackgroundCompat(@ColorInt int normalColor, + @ColorInt int pressedColor, + @ColorInt int rippleColor) { if (Utils.hasLollipop()) { - //TODO: create a color state list for ripple based on state return new RippleDrawable(ColorStateList.valueOf(rippleColor), - getStateListDrawable(normalColor, pressedColor, true), + getStateListDrawable(normalColor, pressedColor), getRippleMask(normalColor)); } else { - return getStateListDrawable(normalColor, pressedColor, false); + return getStateListDrawable(normalColor, pressedColor); } } private static Drawable getRippleMask(@ColorInt int color) { float[] outerRadii = new float[8]; - //3 is the radius of final ripple, instead of 3 we can give required final radius + // 3 is the radius of final ripple, instead of 3 we can give required final radius Arrays.fill(outerRadii, 3); RoundRectShape r = new RoundRectShape(outerRadii, null, null); ShapeDrawable shapeDrawable = new ShapeDrawable(r); @@ -154,21 +192,28 @@ private static Drawable getRippleMask(@ColorInt int color) { } private static StateListDrawable getStateListDrawable(@ColorInt int normalColor, - @ColorInt int pressedColor, - boolean withRipple) { + @ColorInt int pressedColor) { StateListDrawable states = new StateListDrawable(); - if (!withRipple) - states.addState(new int[]{android.R.attr.state_pressed}, getColorDrawable(pressedColor)); states.addState(new int[]{android.R.attr.state_activated}, getColorDrawable(pressedColor)); states.addState(new int[]{}, getColorDrawable(normalColor)); - //Animating across states - int duration = 200; //android.R.integer.config_shortAnimTime - states.setEnterFadeDuration(duration); - states.setExitFadeDuration(duration); + // Animating across states. + // It seems item background is lost on scrolling out of the screen, 21 <= API <= 23 + if (!Utils.hasLollipop() || Utils.hasNougat()) { + int duration = 200; //android.R.integer.config_shortAnimTime + states.setEnterFadeDuration(duration); + states.setExitFadeDuration(duration); + } return states; } - private static ColorDrawable getColorDrawable(@ColorInt int color) { + /** + * Generate the {@code ColorDrawable} object from the provided Color. + * + * @param color the color + * @return the {@code ColorDrawable} object + * @since 5.0.0-rc1 + */ + public static ColorDrawable getColorDrawable(@ColorInt int color) { return new ColorDrawable(color); } diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java index ea45a53a..05f12c51 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/Utils.java @@ -50,6 +50,22 @@ public final class Utils { public static final int INVALID_COLOR = -1; public static int colorAccent = INVALID_COLOR; + /** + * API 24 + * @see VERSION_CODES#N + */ + public static boolean hasNougat() { + return Build.VERSION.SDK_INT >= VERSION_CODES.N; + } + + /** + * API 23 + * @see VERSION_CODES#M + */ + public static boolean hasMarshmallow() { + return Build.VERSION.SDK_INT >= VERSION_CODES.M; + } + /** * API 21 * From edcdb09141de616b6586d25fe276f272e7f085c1 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 18 Dec 2016 23:23:44 +0100 Subject: [PATCH 75/92] Selection methods clearAll() and selectAll() don't notify change anymore. FlexibleViewHolder.toggleSelection() has been improved. Added new method getAllBoundViewHolders(). --- .../flexibleadapter/FlexibleAdapter.java | 18 ++----- .../flexibleadapter/SelectableAdapter.java | 53 ++++++++++++++++--- .../viewholders/FlexibleViewHolder.java | 15 +++--- 3 files changed, 60 insertions(+), 26 deletions(-) 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 6ecefbca..6c2f2069 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -26,7 +26,6 @@ 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; @@ -1840,7 +1839,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { * @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=" + getClassName(holder) + " position=" + position + " itemId=" + holder.getItemId() + " layoutPosition=" + holder.getLayoutPosition()); @@ -1850,24 +1849,15 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List throw new IllegalStateException("AutoMap is not active, this method cannot be called." + " Override this method or implement the AutoMap properly."); } - // 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 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 + // 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)) 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 50966502..f75c8127 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java @@ -26,6 +26,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; @@ -71,6 +73,7 @@ public abstract class SelectableAdapter extends RecyclerView.Adapter } private Set mSelectedPositions; + private Set mBoundViewHolders; private int mMode; protected RecyclerView mRecyclerView; protected FastScroller mFastScroller; @@ -103,6 +106,7 @@ public abstract class SelectableAdapter extends RecyclerView.Adapter public SelectableAdapter() { Log.i("FlexibleAdapter", "Running version " + BuildConfig.VERSION_NAME); mSelectedPositions = new TreeSet<>(); + mBoundViewHolders = new HashSet<>(); mMode = MODE_IDLE; } @@ -351,7 +355,7 @@ public void selectAll(Integer... viewTypes) { mSelectedPositions.add(i); itemCount++; } else { - //Optimization for ItemRangeChanged + // Optimization for ItemRangeChanged if (positionStart + itemCount == i) { notifySelectionChanged(positionStart, itemCount); itemCount = 0; @@ -376,26 +380,63 @@ public void clearSelection() { if (DEBUG) Log.d(TAG, "clearSelection " + mSelectedPositions); Iterator iterator = mSelectedPositions.iterator(); int positionStart = 0, itemCount = 0; - //The notification is done only on items that are currently selected. + // The notification is done only on items that are currently selected. while (iterator.hasNext()) { int position = iterator.next(); iterator.remove(); - //Optimization for ItemRangeChanged + // Optimization for ItemRangeChanged if (positionStart + itemCount == position) { itemCount++; } else { - //Notify previous items in range + // Notify previous items in range notifySelectionChanged(positionStart, itemCount); positionStart = position; itemCount = 1; } } - //Notify remaining items in range + // Notify remaining items in range notifySelectionChanged(positionStart, itemCount); } private void notifySelectionChanged(int positionStart, int itemCount) { - if (itemCount > 0) notifyItemRangeChanged(positionStart, itemCount, Payload.SELECTION); + if (itemCount > 0) { + // Avoid to rebind the VH, direct call to the itemView activation + for (FlexibleViewHolder holder : mBoundViewHolders) { + if (isSelectable(holder.getAdapterPosition())) + holder.toggleActivation(); + } + // Use classic notification, in case FlexibleViewHolder is not implemented + if (mBoundViewHolders.isEmpty()) + notifyItemRangeChanged(positionStart, itemCount, Payload.SELECTION); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) { + // 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; + flexHolder.toggleActivation(); + mBoundViewHolders.add(flexHolder); + } + } + + @Override + public void onViewRecycled(RecyclerView.ViewHolder holder) { + if (holder instanceof FlexibleViewHolder) + mBoundViewHolders.remove(holder); + } + + /** + * Usually {@code RecyclerView} binds 3 items more than the visible items. + * + * @return a Set with all bound FlexibleViewHolders + * @since 5.0.0-rc1 + */ + public Set getAllBoundViewHolders() { + return Collections.unmodifiableSet(mBoundViewHolders); } /** diff --git a/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java b/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java index 2685202b..b3ce33a9 100644 --- a/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java +++ b/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java @@ -214,12 +214,15 @@ protected void setDragHandleView(@NonNull View view) { * @since 5.0.0-b1 */ @CallSuper - protected void toggleActivation() { - itemView.setActivated(mAdapter.isSelected(getFlexibleAdapterPosition())); - if (itemView.isActivated() && getActivationElevation() > 0) - ViewCompat.setElevation(itemView, getActivationElevation()); - else if (getActivationElevation() > 0) //Leave unaltered the default elevation - ViewCompat.setElevation(itemView, 0); + public void toggleActivation() { + boolean selected = mAdapter.isSelected(getFlexibleAdapterPosition()); + if (itemView.isActivated() && !selected || !itemView.isActivated() && selected) { + itemView.setActivated(selected); + if (itemView.isActivated() && getActivationElevation() > 0) + ViewCompat.setElevation(itemView, getActivationElevation()); + else if (getActivationElevation() > 0) //Leave unaltered the default elevation + ViewCompat.setElevation(itemView, 0); + } } /** From 84f59fe01554a2f9e27f0775ff1287c10b1e69bb Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 18 Dec 2016 23:44:12 +0100 Subject: [PATCH 76/92] FlexibleViewHolder.toggleSelection() has been improved. --- .../flexibleadapter/items/SimpleItem.java | 17 +++++++++-------- .../flexibleadapter/SelectableAdapter.java | 6 +++++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java index ab2c7cb8..31a4cac1 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java @@ -93,9 +93,11 @@ public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holde + (getUpdates() > 0 ? " - u" + getUpdates() : "")); } + // Background, when bound the first time if (payloads.size() == 0) { Drawable drawable = DrawableUtils.getSelectableBackgroundCompat( - Color.WHITE, Color.parseColor("#dddddd"), DrawableUtils.getColorControlHighlight(context)); + Color.WHITE, Color.parseColor("#dddddd"), //Same color of divider + DrawableUtils.getColorControlHighlight(context)); DrawableUtils.setBackgroundCompat(holder.itemView, drawable); DrawableUtils.setBackgroundCompat(holder.frontView, drawable); } @@ -110,15 +112,14 @@ public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holde // 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 { + // 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 an SimpleItem's field // this will be highlighted 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 f75c8127..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,6 +18,7 @@ import android.os.Bundle; import android.support.annotation.IntDef; import android.support.annotation.NonNull; +import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; @@ -418,7 +419,10 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List // Bind the correct view elevation if (holder instanceof FlexibleViewHolder) { FlexibleViewHolder flexHolder = (FlexibleViewHolder) holder; - flexHolder.toggleActivation(); + if (holder.itemView.isActivated() && flexHolder.getActivationElevation() > 0) + ViewCompat.setElevation(holder.itemView, flexHolder.getActivationElevation()); + else if (flexHolder.getActivationElevation() > 0) //Leave unaltered the default elevation + ViewCompat.setElevation(holder.itemView, 0); mBoundViewHolders.add(flexHolder); } } From 9589bc10b0b2ebb70c5f131333e606cf97641d56 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Fri, 23 Dec 2016 19:11:29 +0100 Subject: [PATCH 77/92] DemoApp: Separated SimpleItem from ExpandableItem --- .../samples/flexibleadapter/MainActivity.java | 6 +- .../flexibleadapter/items/ExpandableItem.java | 233 +++++++++++++++++- .../items/ExpandableLevel1Item.java | 42 ++-- .../flexibleadapter/items/SimpleItem.java | 81 ++---- .../services/DatabaseService.java | 10 +- .../main/res/layout/recycler_simple_item.xml | 124 ++++++++++ 6 files changed, 407 insertions(+), 89 deletions(-) create mode 100644 flexible-adapter-app/src/main/res/layout/recycler_simple_item.xml 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 e3233238..657b310f 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 @@ -682,8 +682,7 @@ public boolean onItemClick(int position) { return activate; } else { // Notify the active callbacks or implement a custom action onClick - if (!(flexibleItem instanceof ExpandableItem) && flexibleItem instanceof SimpleItem - || flexibleItem instanceof SubItem) { + 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); @@ -874,8 +873,7 @@ public void onDeleteConfirmed(int action) { 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); } 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 index e1c7b39f..e45ec988 100644 --- 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 @@ -1,31 +1,68 @@ 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 (my example: SimpleItem) better to extend directly from + * 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 SimpleItem - implements IExpandable { +public class ExpandableItem extends AbstractItem + implements ISectionable, IFilterable, + IExpandable { - /* Flags for FlexibleAdapter */ - private boolean mExpanded = false; + /* The header of this item */ + HeaderItem header; /* subItems list */ private List mSubItems; + /* Flags for FlexibleAdapter */ + private boolean mExpanded = false; - public ExpandableItem(String id) { + public ExpandableItem(String id, HeaderItem header) { super(id); + this.header = header; + setDraggable(true); + setSwipeable(true); } - public ExpandableItem(String id, HeaderItem header) { - super(id, header); + @Override + public HeaderItem getHeader() { + return header; + } + + @Override + public void setHeader(HeaderItem header) { + this.header = header; } @Override @@ -77,10 +114,182 @@ public void addSubItem(int position, SubItem subItem) { addSubItem(subItem); } -// @Override -// public int getLayoutRes() { -// return R.layout.recycler_expandable_row; -// } + @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() { diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java index eed3018e..30beaf42 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/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.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,8 +21,8 @@ * It's important to note that, the ViewHolder must be specified in all <diamond> signature. */ public class ExpandableLevel1Item - extends AbstractItem - implements IExpandable { + extends AbstractItem + implements IExpandable { /* Flags for FlexibleAdapter */ private boolean mExpanded = false; @@ -71,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); } @@ -88,26 +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.d(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()) { - //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/items/SimpleItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java index 31a4cac1..76c56cdc 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java @@ -8,7 +8,6 @@ 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; @@ -21,29 +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 AbstractItem - implements ISectionable, IFilterable, Serializable { +public class SimpleItem extends AbstractItem + implements ISectionable, IFilterable, Serializable { - /** - * The header of this item - */ + /* The header of this item */ HeaderItem header; - SimpleItem(String id) { + private SimpleItem(String id) { super(id); setDraggable(true); setSwipeable(true); @@ -73,26 +69,19 @@ 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"}) - public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holder, int position, List payloads) { + public void bindViewHolder(final FlexibleAdapter adapter, SimpleViewHolder holder, int position, List payloads) { Context context = holder.itemView.getContext(); - // Subtitle - if (adapter.isExpandable(this)) { - setSubtitle(adapter.getCurrentChildren((IExpandable) 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( @@ -102,34 +91,22 @@ public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holde DrawableUtils.setBackgroundCompat(holder.frontView, drawable); } - if (adapter.isExpandable(this) && 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 + // 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 -// 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 an SimpleItem's 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()); - } + holder.mTitle.setText(getTitle()); + holder.mSubtitle.setText(getSubtitle()); } } @@ -139,10 +116,7 @@ public boolean filter(String constraint) { getSubtitle() != null && getSubtitle().toLowerCase().trim().contains(constraint); } - /** - * This ViewHolder is expandable and collapsible. - */ - static final class ParentViewHolder extends ExpandableViewHolder { + static final class SimpleViewHolder extends FlexibleViewHolder { FlipView mFlipView; TextView mTitle; @@ -155,7 +129,7 @@ static final class ParentViewHolder extends ExpandableViewHolder { public boolean swiped = false; - ParentViewHolder(View view, FlexibleAdapter adapter) { + SimpleViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); this.mContext = view.getContext(); this.mTitle = (TextView) view.findViewById(R.id.title); @@ -204,7 +178,7 @@ public boolean onLongClick(View view) { @Override 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())); } @@ -264,8 +238,7 @@ public void onItemReleased(int 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/services/DatabaseService.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/services/DatabaseService.java index 3d3a575e..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 @@ -480,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); } @@ -566,10 +566,10 @@ public void splitItem(StaggeredItem mainItem, StaggeredItem itemToSplit) { * notified with CHANGE Payload in the Adapter list when refreshed. */ public void updateNewItems() { - for (IFlexible item : mItems) { - if (item instanceof SimpleItem) { - SimpleItem simpleItem = (SimpleItem) item; - simpleItem.increaseUpdates(); + for (IFlexible iFlexible : mItems) { + if (iFlexible instanceof AbstractItem) { + AbstractItem item = (AbstractItem) iFlexible; + item.increaseUpdates(); } } } 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 From 6a13e719569e30f5c091007d2681d05fd330241f Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Fri, 23 Dec 2016 19:38:05 +0100 Subject: [PATCH 78/92] DemoApp: Adjusted SwipeToRefresh behaviors --- .../samples/flexibleadapter/MainActivity.java | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) 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 657b310f..0f0ca1e1 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 @@ -3,7 +3,6 @@ import android.app.SearchManager; import android.content.Context; import android.content.Intent; -import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -145,15 +144,13 @@ public class MainActivity extends AppCompatActivity implements 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: @@ -258,8 +255,8 @@ public void onRefresh() { // 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(); } }); @@ -746,10 +743,11 @@ public void onItemSwipe(final int position, int direction) { message.append(getString(R.string.action_archived)); // Example of UNDO color - int actionTextColor = Color.TRANSPARENT; + int actionTextColor; if (Utils.hasMarshmallow()) { - actionTextColor = getResources().getColor(R.color.material_color_orange_500, this.getTheme()); - } else if (Utils.hasLollipop()) { + actionTextColor = getColor(R.color.material_color_orange_500); + } else { + //noinspection deprecation actionTextColor = getResources().getColor(R.color.material_color_orange_500); } @@ -771,6 +769,7 @@ public boolean onPreAction() { //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) .withAction(UndoHelper.ACTION_REMOVE, new UndoHelper.SimpleActionListener() { @@ -836,8 +835,8 @@ public void onUndoConfirmed(int action) { } else if (action == UndoHelper.ACTION_REMOVE) { // Custom action is restore deleted items mAdapter.restoreDeletedItems(); - // Enable SwipeRefresh - mRefreshHandler.sendEmptyMessage(0); + // Disable Refreshing + mSwipeRefreshLayout.setRefreshing(false); // Check also selection restoration if (mAdapter.isRestoreWithSelection()) { mActionModeHelper.restoreSelection(this); @@ -847,8 +846,8 @@ public void onUndoConfirmed(int action) { @Override public void onDeleteConfirmed(int action) { - // Enable SwipeRefresh - mRefreshHandler.sendEmptyMessage(0); + // Disable Refreshing + mSwipeRefreshLayout.setRefreshing(false); // Removing items from Database. Example: for (AbstractFlexibleItem adapterItem : mAdapter.getDeletedItems()) { try { @@ -934,16 +933,16 @@ public boolean onPreAction() { @Override public void onPostAction() { - // Disable SwipeRefresh + // Enable Refreshing mRefreshHandler.sendEmptyMessage(1); - mRefreshHandler.sendEmptyMessageDelayed(0, 20000); + mRefreshHandler.sendEmptyMessageDelayed(0, 7000); // Finish the action mode mActionModeHelper.destroyActionModeIfCan(); } }) .remove(mAdapter.getSelectedPositions(), findViewById(R.id.main_view), message, - getString(R.string.undo), 20000); + getString(R.string.undo), 7000); // We consume the event return true; @@ -967,7 +966,7 @@ public void onPostAction() { // 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: From c2626f47cc32df8d9ef9ad9165bb3282e3dbfc36 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 25 Dec 2016 23:41:29 +0100 Subject: [PATCH 79/92] Fixed onlyEntryAnimation --- .../flexibleadapter/AnimatorAdapter.java | 44 +++++++++++++------ .../flexibleadapter/FlexibleAdapter.java | 9 ++++ 2 files changed, 39 insertions(+), 14 deletions(-) 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 4c03b026..e5b0c994 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -337,6 +337,14 @@ private void cancelExistingAnimation(final int hashCode) { if (animator != null) animator.end(); } + /** + * 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); + /** * 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 @@ -355,23 +363,23 @@ protected final void animateView(final RecyclerView.ViewHolder holder, final int mMaxChildViews = mRecyclerView.getChildCount(); } // Animate only during initial loading? - if (onlyEntryAnimation && mLastAnimatedPosition == mMaxChildViews) { + 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>LasVisPos=" + (position > lastVisiblePosition) : "") -// + " mMaxChildViews=" + mMaxChildViews -// ); -// } + if (DEBUG) { + Log.v(TAG, "shouldAnimate=" + shouldAnimate + + " isFastScroll=" + isFastScroll + + " isNotified=" + mAnimatorNotifierObserver.isPositionNotified() + + " isReverseEnabled=" + isReverseEnabled + + " mLastAnimatedPosition=" + mLastAnimatedPosition + + (!isReverseEnabled ? " Pos>LasVisPos=" + (position > lastVisiblePosition) : "") + + " mMaxChildViews=" + mMaxChildViews + ); + } if (holder instanceof FlexibleViewHolder && shouldAnimate && !isFastScroll && !mAnimatorNotifierObserver.isPositionNotified() && - (position > lastVisiblePosition || isReverseEnabled || (position == 0 && mMaxChildViews == 0))) { + (position > lastVisiblePosition || isReverseEnabled || isScrollableHeaderOrFooter(position) || (position == 0 && mMaxChildViews == 0))) { // Cancel animation is necessary when fling int hashCode = holder.itemView.hashCode(); @@ -386,10 +394,18 @@ protected final void animateView(final RecyclerView.ViewHolder holder, final int 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 + // Stop stepDelay when screen is filled set.setStartDelay(calculateAnimationDelay(position)); } set.start(); 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 6c2f2069..77a798df 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -759,6 +759,15 @@ 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: From e2f67ff9589241c8e93ce953f035837c60487696 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 25 Dec 2016 23:42:21 +0100 Subject: [PATCH 80/92] Added ScrollableUseCaseItem to Overall fragment --- .../flexibleadapter/ExampleAdapter.java | 1 - .../samples/flexibleadapter/MainActivity.java | 48 ++++++++++++++----- .../flexibleadapter/OverallAdapter.java | 43 ++++++++++++----- .../fragments/FragmentOverall.java | 16 +++++-- .../fragments/FragmentSelectionModes.java | 5 -- .../items/ScrollableFooterItem.java | 2 +- .../items/ScrollableULSItem.java | 4 +- .../items/ScrollableUseCaseItem.java | 32 ++++++------- .../recycler_scrollable_usecase_item.xml | 8 ++-- .../main/res/menu/activity_entry_drawer.xml | 1 - .../src/main/res/values/strings.xml | 7 ++- .../helpers/AnimatorHelper.java | 39 ++++++++++++--- .../helpers/StickyHeaderHelper.java | 18 +++---- 13 files changed, 150 insertions(+), 74 deletions(-) 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 2c05310e..c9a82a40 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 @@ -34,7 +34,6 @@ 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) { 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 0f0ca1e1..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 @@ -3,6 +3,7 @@ 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; @@ -159,7 +160,9 @@ public boolean handleMessage(Message message) { } }); - /* ACTIVITY MANAGEMENT */ + /* =================== + * ACTIVITY MANAGEMENT + * =================== */ @Override protected void onCreate(Bundle savedInstanceState) { @@ -214,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) { @@ -311,7 +316,9 @@ public void onFastScrollerStateChange(boolean scrolling) { } } - /* NAVIGATION DRAWER & FRAGMENT MANAGEMENT */ + /* ======================================= + * NAVIGATION DRAWER & FRAGMENT MANAGEMENT + * ======================================= */ /** * IMPORTANT!! READ THE COMMENT FOR THE FRAGMENT REPLACE @@ -371,7 +378,10 @@ public void run() { .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) { @@ -402,7 +412,9 @@ public void run() { return false; } - /* FLOATING ACTION BUTTON */ + /* ====================== + * FLOATING ACTION BUTTON + * ====================== */ private void hideFabSilently() { mFab.setAlpha(0f); @@ -427,7 +439,9 @@ private void showFab() { } } - /* SEARCH VIEW */ + /* =========== + * SEARCH VIEW + * =========== */ @Override public void initSearchView(final Menu menu) { @@ -485,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) { @@ -644,7 +660,9 @@ 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) { @@ -660,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) { @@ -880,7 +902,9 @@ public void onDeleteConfirmed(int action) { } } - /* ACTION MODE IMPLEMENTATION */ + /* ========================== + * ACTION MODE IMPLEMENTATION + * ========================== */ @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { @@ -1008,7 +1032,9 @@ public void onDestroyActionMode(ActionMode mode) { } } - /* EXTRAS */ + /* ====== + * EXTRAS + * ====== */ @Override public void onBackPressed() { 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 45527898..68ced98e 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 @@ -15,6 +15,7 @@ import eu.davidea.flexibleadapter.items.IFlexible; 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; @@ -62,7 +63,7 @@ public void showLayoutInfo(boolean scrollToPosition) { String.valueOf(eu.davidea.flexibleadapter.utils.Utils.getSpanCount(mRecyclerView.getLayoutManager()))) ); addScrollableHeaderWithDelay(item, 500L, scrollToPosition); - removeScrollableHeaderWithDelay(item, 2000L); + removeScrollableHeaderWithDelay(item, 3000L); } } @@ -73,7 +74,8 @@ public void showLayoutInfo(boolean scrollToPosition) { @Override public int getItemViewType(int position) { IFlexible item = getItem(position); - if (item instanceof ScrollableLayoutItem) return R.layout.recycler_scrollable_layout_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; } @@ -85,6 +87,10 @@ public int getItemViewType(int position) { public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { mInflater = LayoutInflater.from(parent.getContext()); switch (viewType) { + 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); @@ -103,7 +109,22 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payload) { int viewType = getItemViewType(position); - if (viewType == R.layout.recycler_scrollable_layout_item) { + + if (viewType == R.layout.recycler_scrollable_usecase_item) { + ScrollableUseCaseItem item = (ScrollableUseCaseItem) getItem(position); + ScrollableUseCaseItem.UCViewHolder vHolder = (ScrollableUseCaseItem.UCViewHolder) holder; + assert item != null; + + 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; @@ -134,15 +155,15 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List if (item.getIcon() != null) { 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)); } + + // 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/fragments/FragmentOverall.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentOverall.java index 802013af..bd4d03b3 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 @@ -10,11 +10,13 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.animation.DecelerateInterpolator; import eu.davidea.flexibleadapter.SelectableAdapter; 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; /** @@ -66,11 +68,10 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mAdapter = new OverallAdapter(getActivity()); // Experimenting NEW features (v5.0.0) - mAdapter.setAnimationOnScrolling(true) - .setAnimationOnReverseScrolling(true) - //.setAnimationInterpolator(new DecelerateInterpolator()) + 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); @@ -88,7 +89,7 @@ public void run() { Snackbar.make(getView(), "Long press drag is enabled", Snackbar.LENGTH_SHORT).show(); } } - }, 1500L); + }, 3000L); SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout); swipeRefreshLayout.setEnabled(true); @@ -96,6 +97,10 @@ public void run() { // Add 1 Scrollable Header mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.addScrollableHeader( + new ScrollableUseCaseItem( + getString(R.string.overall_use_case_title), + getString(R.string.overall_use_case_description))); } @Override @@ -120,6 +125,7 @@ public int getSpanSize(int position) { // 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_scrollable_usecase_item: case R.layout.recycler_scrollable_layout_item: return mColumnCount; default: 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 11c68e25..1bd4f08d 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 @@ -31,11 +31,6 @@ public class FragmentSelectionModes extends AbstractFragment { public static final String TAG = FragmentSelectionModes.class.getSimpleName(); - /** - * The current activated item position. - */ - private int mActivatedPosition = RecyclerView.NO_POSITION; - /** * Custom implementation of FlexibleAdapter */ 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 index cc96c0b9..d52f5321 100644 --- 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 @@ -60,7 +60,7 @@ class FooterViewHolder extends FlexibleViewHolder implements AnimatedViewHolder mDismissIcon.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - //Don't need anymore to set permanent for Scrollable Headers and Footers + //Don't need anymore to set permanent deletion for Scrollable Headers and Footers //mAdapter.setPermanentDelete(true); //noinspection unchecked mAdapter.removeScrollableFooter(ScrollableFooterItem.this); diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java index c2493822..904cf5e0 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java @@ -19,7 +19,7 @@ import eu.davidea.viewholders.FlexibleViewHolder; /** - * Item dedicated only for User Learns Selection view (located always at position 0 in the Adapter). + * Item dedicated only for User Learns Selection view (located always at the top in the Adapter). * This item is a Scrollable Header. */ public class ScrollableULSItem extends AbstractItem { @@ -67,7 +67,7 @@ class ULSViewHolder extends FlexibleViewHolder { @Override public void onClick(View v) { DatabaseConfiguration.userLearnedSelection = true; - //Don't need anymore to set permanent for Scrollable Headers and Footers + //Don't need anymore to set permanent deletion for Scrollable Headers and Footers //mAdapter.setPermanentDelete(true); //noinspection unchecked mAdapter.removeScrollableHeader(ScrollableULSItem.this); 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 index ce6c1c40..b182b767 100644 --- 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 @@ -13,6 +13,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.helpers.AnimatorHelper; +import eu.davidea.flexibleadapter.items.IHeader; import eu.davidea.samples.flexibleadapter.R; import eu.davidea.utils.Utils; import eu.davidea.viewholders.FlexibleViewHolder; @@ -20,15 +21,13 @@ /** * This item is a Scrollable Header. */ -public class ScrollableUseCaseItem extends AbstractItem { +public class ScrollableUseCaseItem extends AbstractItem + implements IHeader { - public ScrollableUseCaseItem(String id) { - super(id); - } - - @Override - public boolean isSelectable() { - return false; + public ScrollableUseCaseItem(String title, String subTitle) { + super("UC"); + setTitle(title); + setSubtitle(subTitle); } @Override @@ -47,13 +46,13 @@ public void bindViewHolder(FlexibleAdapter adapter, UCViewHolder holder, int pos holder.mSubtitle.setText(Utils.fromHtmlCompat(getSubtitle())); } - class UCViewHolder extends FlexibleViewHolder { + public static class UCViewHolder extends FlexibleViewHolder { - TextView mTitle; - TextView mSubtitle; + public TextView mTitle; + public TextView mSubtitle; ImageView mDismissIcon; - UCViewHolder(View view, FlexibleAdapter adapter) { + public UCViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); mTitle = (TextView) view.findViewById(R.id.title); mSubtitle = (TextView) view.findViewById(R.id.subtitle); @@ -61,10 +60,10 @@ class UCViewHolder extends FlexibleViewHolder { mDismissIcon.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - //Don't need anymore to set permanent for Scrollable Headers and Footers + //Don't need anymore to set permanent deletion for Scrollable Headers and Footers //mAdapter.setPermanentDelete(true); - //noinspection unchecked - mAdapter.removeScrollableHeader(ScrollableUseCaseItem.this); + //noinspection unchecked, ConstantConditions + mAdapter.removeScrollableHeader(mAdapter.getItem(getAdapterPosition())); //mAdapter.setPermanentDelete(false); } }); @@ -77,7 +76,8 @@ public void onClick(View v) { @Override public void scrollAnimators(@NonNull List animators, int position, boolean isForward) { - AnimatorHelper.slideInFromTopAnimator(animators, itemView, mAdapter.getRecyclerView()); + AnimatorHelper.flipAnimator(animators, itemView); + AnimatorHelper.setDuration(animators, 500L); } } diff --git a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml index 7c49b449..d6a6d091 100644 --- a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml +++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml @@ -35,9 +35,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" - android:layout_marginEnd="@dimen/margin_right" android:layout_marginLeft="@dimen/margin_left" - android:layout_marginRight="@dimen/margin_right" android:layout_marginStart="@dimen/margin_left" android:layout_toEndOf="@id/image" android:layout_toLeftOf="@id/image_util_container" @@ -52,7 +50,7 @@ android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" android:maxLines="1" - android:text="@string/endless_scrolling" + android:text="@string/overall_use_case_title" android:textAppearance="@style/TextAppearance.AppCompat.Title" android:textColor="?primaryTextSelector"/> @@ -61,8 +59,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="2dp" - android:text="@string/endless_scrolling_description" - android:textAppearance="@style/TextAppearance.AppCompat.Small" + android:text="@string/overall_use_case_description" + android:textAppearance="@style/TextAppearance.AppCompat.Notification.Info" android:textColor="?primaryTextSelector"/> 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 c7c218f7..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 @@ -83,7 +83,6 @@ android:title="@string/about_title"/> diff --git a/flexible-adapter-app/src/main/res/values/strings.xml b/flexible-adapter-app/src/main/res/values/strings.xml index 500abebe..6c72f3ad 100644 --- a/flexible-adapter-app/src/main/res/values/strings.xml +++ b/flexible-adapter-app/src/main/res/values/strings.xml @@ -71,6 +71,7 @@ STATUS E Scrolling Animations Item Animators + Complete action using Configure the list ... @@ -95,6 +96,10 @@ items and higher, due to calculate the correct position for each item to shift. Overall + Showcase of the library capabilities + fragments where the + RecyclerView and the Adapter are reinitialized in each fragment which can use + several features, but the MainActivity implements and reuses the common listeners.]]> Selection modes IDLE, SINGLE, MULTI + ActionModeHelper + UndoHelper + Scrollable Headers @@ -136,7 +141,7 @@ items and higher, due to calculate the correct position for each item to shift. Flexible Adapter - GitHub page + GitHub page… About %1$s #%2$s animators, @NonNull View view) { + alphaAnimator(animators, view, 0f); + animators.add(ObjectAnimator.ofFloat(view, "scaleY", 0f, 1f)); + if (FlexibleAdapter.DEBUG) Log.v(TAG, " Added FLIP Animator"); + } + + /** + * Adds a custom duration to the current view. + * + * @param animators user defined list of animators + * @param duration duration in milliseconds + */ + public static void setDuration(@NonNull List animators, @IntRange(from = 0) long duration) { + if (animators.size() > 0) { + Animator animator = animators.get(animators.size() - 1); + animator.setDuration(duration); + } + } + } \ No newline at end of file diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java index 4bb2f44a..376263f5 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/StickyHeaderHelper.java @@ -89,23 +89,22 @@ public void detachFromRecyclerView() { if (FlexibleAdapter.DEBUG) Log.i(TAG, "StickyHolderLayout detached"); } -// private FrameLayout createContainer(int width, int height) { -// FrameLayout frameLayout = new FrameLayout(mRecyclerView.getContext()); -// frameLayout.setLayoutParams(new ViewGroup.LayoutParams(width, height)); -// return frameLayout; -// } + private FrameLayout createContainer(int width, int height) { + FrameLayout frameLayout = new FrameLayout(mRecyclerView.getContext()); + frameLayout.setLayoutParams(new ViewGroup.LayoutParams(width, height)); + return frameLayout; + } - private static ViewGroup getParent(View view) { + private ViewGroup getParent(View view) { return (ViewGroup) view.getParent(); } private void initStickyHeadersHolder() { if (mStickyHolderLayout == null) { // Create stickyContainer for shadow elevation - FrameLayout stickyContainer = new FrameLayout(mRecyclerView.getContext()); - stickyContainer.setLayoutParams(new ViewGroup.LayoutParams( + FrameLayout stickyContainer = createContainer( ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); + ViewGroup.LayoutParams.WRAP_CONTENT); ViewGroup oldParentLayout = getParent(mRecyclerView); oldParentLayout.addView(stickyContainer); // Initialize Holder Layout @@ -134,6 +133,7 @@ private void onStickyHeaderChange(int sectionIndex) { } public void updateOrClearHeader(boolean updateHeaderContent) { + // if (!mAdapter.areHeadersShown() || mAdapter.hasSearchText() || mAdapter.getItemCount() == 0) { clearHeaderWithAnimation(); return; From 22db5536a67f7b7a12508b1a414359062474585d Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 26 Dec 2016 14:43:53 +0100 Subject: [PATCH 81/92] Fixed activated position in ActionModeHelper --- .../helpers/ActionModeHelper.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ActionModeHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ActionModeHelper.java index d7e5760e..83a75fa0 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ActionModeHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/ActionModeHelper.java @@ -25,6 +25,8 @@ import android.view.Menu; import android.view.MenuItem; +import java.util.List; + import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.SelectableAdapter; import eu.davidea.flexibleadapter.SelectableAdapter.Mode; @@ -43,7 +45,6 @@ public class ActionModeHelper implements ActionMode.Callback { private int defaultMode = SelectableAdapter.MODE_IDLE; @MenuRes private int mCabMenu; - private int mActivatedPosition; private FlexibleAdapter mAdapter; private ActionMode.Callback mCallback; protected ActionMode mActionMode; @@ -59,7 +60,6 @@ public class ActionModeHelper implements ActionMode.Callback { public ActionModeHelper(@NonNull FlexibleAdapter adapter, @MenuRes int cabMenu) { this.mAdapter = adapter; this.mCabMenu = cabMenu; - this.mActivatedPosition = RecyclerView.NO_POSITION; } /** @@ -102,13 +102,18 @@ public ActionMode getActionMode() { } /** - * Gets the last activated position, especially useful in {@code MODE_SINGLE}. + * Gets the activated position only when mode is {@code MODE_SINGLE}. * - * @return the last activated position, -1 if no item is selected + * @return the activated position when {@code MODE_SINGLE}. -1 if no item is selected * @since 5.0.0-rc1 */ public int getActivatedPosition() { - return mActivatedPosition; + List selectedPositions = mAdapter.getSelectedPositions(); + if (mAdapter.getMode() == SelectableAdapter.MODE_SINGLE && + selectedPositions.size() == 1) { + return selectedPositions.get(0); + } + return RecyclerView.NO_POSITION; } /** @@ -156,9 +161,8 @@ public ActionMode onLongClick(AppCompatActivity activity, int position) { */ public void toggleSelection(int position) { if (position >= 0 && ( - (mAdapter.getMode() == SelectableAdapter.MODE_SINGLE && position != mActivatedPosition) || + (mAdapter.getMode() == SelectableAdapter.MODE_SINGLE && !mAdapter.isSelected(position)) || mAdapter.getMode() == SelectableAdapter.MODE_MULTI)) { - mActivatedPosition = position; mAdapter.toggleSelection(position); } // If MODE_SINGLE is active then ActionMode can be null @@ -246,7 +250,6 @@ public void onDestroyActionMode(ActionMode actionMode) { Log.i(TAG, "ActionMode is about to be destroyed! New mode will be " + defaultMode); // Change mode and deselect everything mAdapter.setMode(defaultMode); - mActivatedPosition = RecyclerView.NO_POSITION; mAdapter.clearSelection(); mActionMode = null; // Notify the provided callback From 98a5e49ffa26f90829f354775e73c40344f87331 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 26 Dec 2016 23:40:07 +0100 Subject: [PATCH 82/92] Only selectable items can be activated with toggleActivation() --- .../viewholders/FlexibleViewHolder.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java b/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java index b3ce33a9..bdec9c7f 100644 --- a/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java +++ b/flexible-adapter/src/main/java/eu/davidea/viewholders/FlexibleViewHolder.java @@ -52,16 +52,16 @@ public abstract class FlexibleViewHolder extends ContentViewHolder private static final String TAG = FlexibleViewHolder.class.getSimpleName(); - //FlexibleAdapter is needed to retrieve listeners and item status + // FlexibleAdapter is needed to retrieve listeners and item status protected final FlexibleAdapter mAdapter; - //These 2 fields avoid double tactile feedback triggered by Android during the touch event + // These 2 fields avoid double tactile feedback triggered by Android during the touch event // (Drag or Swipe), also assure the LongClick event is correctly fired for ActionMode if that // was the user intention. private boolean mLongClickSkipped = false; private boolean alreadySelected = false; - //State for Dragging & Swiping actions + // State for Dragging & Swiping actions protected int mActionState = ItemTouchHelper.ACTION_STATE_IDLE; /*--------------*/ @@ -112,18 +112,15 @@ public FlexibleViewHolder(View view, FlexibleAdapter adapter, boolean stickyHead public void onClick(View view) { int position = getFlexibleAdapterPosition(); if (!mAdapter.isEnabled(position)) return; - //Experimented that, if LongClick is not consumed, onClick is fired. We skip the - //call to the listener in this case, which is allowed only in ACTION_STATE_IDLE. + // Experimented that, if LongClick is not consumed, onClick is fired. We skip the + // call to the listener in this case, which is allowed only in ACTION_STATE_IDLE. if (mAdapter.mItemClickListener != null && mActionState == ItemTouchHelper.ACTION_STATE_IDLE) { if (FlexibleAdapter.DEBUG) Log.v(TAG, "onClick on position " + position + " mode=" + mAdapter.getMode()); - //Get the permission to activate the View from user + // Get the permission to activate the View from user if (mAdapter.mItemClickListener.onItemClick(position)) { - //Now toggle the activation - if (!mAdapter.isSelected(position) && itemView.isActivated() || - mAdapter.isSelected(position) && !itemView.isActivated()) { - toggleActivation(); - } + // Now toggle the activation + toggleActivation(); } } } @@ -141,7 +138,7 @@ public boolean onLongClick(View view) { if (!mAdapter.isEnabled(position)) return false; if (FlexibleAdapter.DEBUG) Log.v(TAG, "onLongClick on position " + position + " mode=" + mAdapter.getMode()); - //If DragLongPress is enabled, then LongClick must be skipped and the listener will + // If DragLongPress is enabled, then LongClick must be skipped and the listener will // be called in onActionStateChanged in Drag mode. if (mAdapter.mItemLongClickListener != null && !mAdapter.isLongPressDragEnabled()) { mAdapter.mItemLongClickListener.onItemLongClick(position); @@ -215,9 +212,14 @@ protected void setDragHandleView(@NonNull View view) { */ @CallSuper public void toggleActivation() { - boolean selected = mAdapter.isSelected(getFlexibleAdapterPosition()); + // Only for selectable items + int position = getFlexibleAdapterPosition(); + if (!mAdapter.isSelectable(position)) return; + // [De]Activate the view + boolean selected = mAdapter.isSelected(position); if (itemView.isActivated() && !selected || !itemView.isActivated() && selected) { itemView.setActivated(selected); + // Apply elevation if (itemView.isActivated() && getActivationElevation() > 0) ViewCompat.setElevation(itemView, getActivationElevation()); else if (getActivationElevation() > 0) //Leave unaltered the default elevation From c700e3b488367ccd6d80061b48f87dff5c911947 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 26 Dec 2016 23:40:47 +0100 Subject: [PATCH 83/92] Added new static method for RippleDrawable in DrawableUtils --- .../flexibleadapter/utils/DrawableUtils.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java index d3235c96..5c25e92d 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/utils/DrawableUtils.java @@ -17,6 +17,7 @@ import android.content.Context; import android.content.res.ColorStateList; +import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; @@ -181,6 +182,24 @@ public static Drawable getSelectableBackgroundCompat(@ColorInt int normalColor, } } + /** + * Adds a ripple effect to any background. + * + * @param drawable any background drawable + * @param rippleColor the color of the ripple + * @return the RippleDrawable with the chosen background drawable if at least Lollipop, + * the provided drawable otherwise + * @since 5.0.0-rc1 + */ + public static Drawable getRippleDrawable(Drawable drawable, @ColorInt int rippleColor) { + if (Utils.hasLollipop()) { + return new RippleDrawable(ColorStateList.valueOf(rippleColor), + drawable, getRippleMask(Color.BLACK)); + } else { + return drawable; + } + } + private static Drawable getRippleMask(@ColorInt int color) { float[] outerRadii = new float[8]; // 3 is the radius of final ripple, instead of 3 we can give required final radius From 0107c4b54f6ea41332ad431add9754f4bdfb2196 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Mon, 26 Dec 2016 23:41:31 +0100 Subject: [PATCH 84/92] Tuning the demoApp --- .../samples/flexibleadapter/ExampleAdapter.java | 4 ++-- .../samples/flexibleadapter/OverallAdapter.java | 8 ++++++++ .../flexibleadapter/fragments/FragmentOverall.java | 9 ++++----- .../fragments/FragmentSelectionModes.java | 6 +++++- .../flexibleadapter/items/ScrollableUseCaseItem.java | 7 +++++++ .../res/layout/recycler_scrollable_usecase_item.xml | 4 ++-- flexible-adapter-app/src/main/res/values/strings.xml | 10 +++++++--- 7 files changed, 35 insertions(+), 13 deletions(-) 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 c9a82a40..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 @@ -92,7 +92,7 @@ public void showLayoutInfo(boolean scrollToPosition) { // 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, 0L, scrollToPosition); + addScrollableHeaderWithDelay(item, 1200L, scrollToPosition); removeScrollableHeaderWithDelay(item, 4000L); } } @@ -107,7 +107,7 @@ public void addUserLearnedSelection(boolean scrollToPosition) { final ScrollableULSItem item = new ScrollableULSItem("ULS"); item.setTitle(mRecyclerView.getContext().getString(R.string.uls_title)); item.setSubtitle(mRecyclerView.getContext().getString(R.string.uls_subtitle)); - addScrollableHeaderWithDelay(item, 1000L, scrollToPosition); + addScrollableHeaderWithDelay(item, 1000L, false); } } 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 68ced98e..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,6 +14,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; import eu.davidea.flexibleadapter.items.IFlexible; +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; @@ -109,12 +111,18 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payload) { int viewType = getItemViewType(position); + 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())); 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 bd4d03b3..2c8b80bf 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 @@ -95,12 +95,11 @@ public void run() { swipeRefreshLayout.setEnabled(true); mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); - // Add 1 Scrollable Header + // 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))); + mAdapter.addScrollableHeader(new ScrollableUseCaseItem( + getString(R.string.overall_use_case_title), + getString(R.string.overall_use_case_description))); } @Override 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 1bd4f08d..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 @@ -19,6 +19,7 @@ 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; @@ -106,7 +107,10 @@ public void run() { // 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 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 index b182b767..d3f507d8 100644 --- 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 @@ -1,6 +1,7 @@ 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; @@ -14,6 +15,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.helpers.AnimatorHelper; import eu.davidea.flexibleadapter.items.IHeader; +import eu.davidea.flexibleadapter.utils.DrawableUtils; import eu.davidea.samples.flexibleadapter.R; import eu.davidea.utils.Utils; import eu.davidea.viewholders.FlexibleViewHolder; @@ -42,6 +44,11 @@ public UCViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inf @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())); } diff --git a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml index d6a6d091..da6a7872 100644 --- a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml +++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_usecase_item.xml @@ -15,8 +15,8 @@ android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_centerInParent="true" - android:layout_marginEnd="@dimen/margin_small" - android:layout_marginRight="@dimen/margin_small"> + android:layout_marginRight="@dimen/margin_right" + android:layout_marginEnd="@dimen/margin_right"> Overall Showcase of the library capabilities - fragments where the - RecyclerView and the Adapter are reinitialized in each fragment which can use - several features, but the MainActivity implements and reuses the common listeners.]]> + 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 + 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 From 8ae9c7cf92d191342d171fa5952a0991f5c29765 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 28 Dec 2016 00:05:39 +0100 Subject: [PATCH 85/92] Preparation for rc1 pre-release --- README.md | 9 +++++---- build.gradle | 4 ++-- flexible-adapter-app/src/main/res/values/strings.xml | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 06aaeb45..6a3f4ef6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ####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 2016.12.xx (157KB) +- **NEW!** First release candidate: [v5.0.0-rc1](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-rc1) built on 2016.12.29 (159KB) - 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.
      @@ -18,7 +18,7 @@ Since I created this library, it has become easy to configure how views will be 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 ViewHolders and new features. #### Main features * Simple item selection with ripple effect, Single & Multi selection mode. @@ -30,7 +30,7 @@ This library is configurable and it guides the developers to create a better use * **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 and automatic linkage! +* **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_. @@ -101,7 +101,7 @@ You can download the latest demo App from the latest release page OR run it with # Change Log ###### Latest release -[v5.0.0-rc1](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-rc1) - 2016.12.xx +[v5.0.0-rc1](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-rc1) - 2016.12.29 ###### Old releases [v5.0.0-b8](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b8) - 2016.09.17 | @@ -141,6 +141,7 @@ Special thanks goes to Martin Guillon ([Akylas](https://github.com/Akylas)) to h # Apps that use this Adapter 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) diff --git a/build.gradle b/build.gradle index d066838f..3c4237bd 100644 --- a/build.gradle +++ b/build.gradle @@ -7,9 +7,9 @@ ext { //Library libraryCode = 19 - libraryVersion = "5.0.0-SNAPSHOT" + libraryVersion = "5.0.0-rc1" libraryDate = " built on " + getDate() - libraryDescription = "1 Adapter for SelectionMode, ViewHolders, AsyncFilter, FastScroller, Animations, Undo, Sections, Headers, Footers, EndlessScroll, Expandable, Draggable, Swipeable :-)" + 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" diff --git a/flexible-adapter-app/src/main/res/values/strings.xml b/flexible-adapter-app/src/main/res/values/strings.xml index 92932789..38affe89 100644 --- a/flexible-adapter-app/src/main/res/values/strings.xml +++ b/flexible-adapter-app/src/main/res/values/strings.xml @@ -91,8 +91,8 @@ 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 @@ -159,7 +159,7 @@ the library. Common listeners are instead implemented in the MainActivity 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 From 1f422aade6487064f4e0384b31ece90553bfa6b2 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 28 Dec 2016 23:38:47 +0100 Subject: [PATCH 86/92] DemoApp: added more use case items as Scrollable Headers --- .../fragments/FragmentAnimators.java | 5 ++- .../fragments/FragmentHeadersSections.java | 7 +++- .../fragments/FragmentHolderSections.java | 8 +++-- .../fragments/FragmentOverall.java | 2 +- .../fragments/FragmentStaggeredLayout.java | 21 ++++++------ .../items/ScrollableUseCaseItem.java | 4 +-- .../items/StaggeredHeaderItem.java | 2 -- .../flexibleadapter/items/StaggeredItem.java | 2 +- .../res/layout/fragment_recycler_view.xml | 16 +++------ .../main/res/layout/fragment_view_pager.xml | 22 ++++-------- .../layout/recycler_staggered_header_item.xml | 34 +++++++------------ .../src/main/res/values/strings.xml | 23 +++++++++++-- 12 files changed, 73 insertions(+), 73 deletions(-) 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 9a54f030..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; @@ -116,7 +117,9 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); // Add 1 Scrollable Header - mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.addScrollableHeader(new ScrollableUseCaseItem( + getString(R.string.animator_use_case_title), + getString(R.string.animator_use_case_description))); } @Override 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 5d3ca98d..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 @@ -24,6 +24,7 @@ import eu.davidea.samples.flexibleadapter.dialogs.BottomSheetDialog; import eu.davidea.samples.flexibleadapter.dialogs.OnParameterSelectedListener; 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; @@ -111,8 +112,11 @@ private void initializeRecyclerView(Bundle savedInstanceState) { swipeRefreshLayout.setEnabled(true); mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); - // Add 2 Scrollable Headers and 1 Footer + // 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(); } @@ -189,6 +193,7 @@ public int getSpanSize(int position) { // 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_scrollable_usecase_item: case R.layout.recycler_scrollable_header_item: case R.layout.recycler_scrollable_footer_item: case R.layout.recycler_scrollable_layout_item: 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 index 9b1d8bf4..c4a63e2f 100644 --- 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 @@ -13,6 +13,7 @@ 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; @@ -68,14 +69,17 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mAdapter.setFastScroller((FastScroller) getView().findViewById(R.id.fast_scroller), Utils.getColorAccent(getActivity()), (MainActivity) getActivity()); mAdapter.setDisplayHeadersAtStartUp(true) - .setStickyHeaders(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.addUserLearnedSelection(savedInstanceState == null); + mAdapter.addScrollableHeader(new ScrollableUseCaseItem( + getString(R.string.model_holders_use_case_title), + getString(R.string.model_holders_use_case_description))); } @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 2c8b80bf..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 @@ -89,7 +89,7 @@ public void run() { Snackbar.make(getView(), "Long press drag is enabled", Snackbar.LENGTH_SHORT).show(); } } - }, 3000L); + }, 4000L); SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout); swipeRefreshLayout.setEnabled(true); 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 00ba4f72..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,13 +12,15 @@ 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.items.ScrollableUseCaseItem; import eu.davidea.samples.flexibleadapter.items.StaggeredHeaderItem; import eu.davidea.samples.flexibleadapter.items.StaggeredItem; import eu.davidea.samples.flexibleadapter.items.StaggeredItemStatus; @@ -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(); @@ -67,7 +69,7 @@ public void onActivityCreated(Bundle savedInstanceState) { 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 = 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 @@ -88,14 +90,17 @@ private void initializeRecyclerView(Bundle savedInstanceState) { // Experimenting NEW features (v5.0.0) mAdapter.setDisplayHeadersAtStartUp(true) //Show Headers at startUp! .setNotifyMoveOfFilteredItems(true) - .setPermanentDelete(true); //Default=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 1 Scrollable Header - mAdapter.showLayoutInfo(savedInstanceState == null); + mAdapter.addScrollableHeader(new ScrollableUseCaseItem( + getString(R.string.staggered_use_case_title), + getString(R.string.staggered_use_case_description))); } @Override @@ -103,12 +108,6 @@ public int getContextMenuResId() { return R.menu.menu_staggered_context; } - @Override - public void showNewLayoutInfo(MenuItem item) { - super.showNewLayoutInfo(item); - mAdapter.showLayoutInfo(false); - } - @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); 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 index d3f507d8..d531c3cd 100644 --- 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 @@ -14,7 +14,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.helpers.AnimatorHelper; -import eu.davidea.flexibleadapter.items.IHeader; import eu.davidea.flexibleadapter.utils.DrawableUtils; import eu.davidea.samples.flexibleadapter.R; import eu.davidea.utils.Utils; @@ -23,8 +22,7 @@ /** * This item is a Scrollable Header. */ -public class ScrollableUseCaseItem extends AbstractItem - implements IHeader { +public class ScrollableUseCaseItem extends AbstractItem { public ScrollableUseCaseItem(String title, String subTitle) { super("UC"); diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredHeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredHeaderItem.java index 5bec4530..ec10506b 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredHeaderItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredHeaderItem.java @@ -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/items/StaggeredItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java index 281d22eb..d3aecb72 100644 --- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java +++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java @@ -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); } 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 4113ab17..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 @@ -12,8 +12,9 @@ android:layout_height="match_parent" 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 index 7c730e32..b5fbeecd 100644 --- a/flexible-adapter-app/src/main/res/layout/fragment_view_pager.xml +++ b/flexible-adapter-app/src/main/res/layout/fragment_view_pager.xml @@ -9,11 +9,11 @@ - - + - - + - - - - - - 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/values/strings.xml b/flexible-adapter-app/src/main/res/values/strings.xml index 38affe89..c2cdc435 100644 --- a/flexible-adapter-app/src/main/res/values/strings.xml +++ b/flexible-adapter-app/src/main/res/values/strings.xml @@ -37,7 +37,7 @@ Click on Image (or LongClick on Item) to select that Item
      ]]> - This is scrollable header.]]> + This is a scrollable header.]]> Scrollable Header Item Scrollable Headers are always displayed at the top of all main items.]]> Scrollable Expandable Header Item @@ -105,17 +105,27 @@ the library. Common listeners are instead implemented in the MainActivity 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.]]>
      +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 + 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 @@ -131,11 +141,18 @@ the library. Common listeners are instead implemented in the MainActivity 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 + Support for Staggered Layout + calculatePositionFor().
      +Example of dynamic background with ripple effect without XML configuration.]]>
      View Pager Example usage with Sticky Headers in ViewPager layout From 8aea35ca1840c60ceb03f35ca54fda4657915222 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Wed, 11 Jan 2017 17:32:04 +0100 Subject: [PATCH 87/92] Improved sample for EndlessScroll feature --- .../fragments/FragmentEndlessScrolling.java | 41 ++++++------ .../flexibleadapter/items/ProgressItem.java | 64 +++++++++++++++---- .../src/main/res/values/strings.xml | 4 +- .../flexibleadapter/AnimatorAdapter.java | 26 ++++---- 4 files changed, 90 insertions(+), 45 deletions(-) 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 97f45b27..adcec680 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 @@ -43,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(); @@ -108,7 +109,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) { mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE); // EndlessScrollListener - OnLoadMore (v5.0.0) - mAdapter.setEndlessScrollListener(this, new ProgressItem()) + 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 @@ -124,24 +125,24 @@ public void showNewLayoutInfo(MenuItem item) { 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()); - } +/** + * 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. @@ -170,7 +171,7 @@ public void run() { // 1. Simulating success/failure with Random int count = new Random().nextInt(7); - int totalItemsOfType = mAdapter.getItemCountOfTypes(R.layout.recycler_expandable_item); + int totalItemsOfType = mAdapter.getItemCountOfTypes(R.layout.recycler_simple_item); for (int i = 1; i <= count; i++) { newItems.add(DatabaseService.newSimpleItem(totalItemsOfType + i, null)); } 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 index 5e5d3c4a..e11a960c 100644 --- 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 @@ -27,11 +27,21 @@ */ 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; @@ -43,19 +53,43 @@ public ProgressViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflat } @Override - public void bindViewHolder(FlexibleAdapter adapter, ProgressViewHolder holder, int position, List payloads) { - if (!payloads.contains(Payload.NO_MORE_LOAD) && adapter.isEndlessScrollEnabled()) { + 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); - } else { - holder.progressBar.setVisibility(View.GONE); - holder.progressMessage.setVisibility(View.VISIBLE); - Context context = holder.itemView.getContext(); - if (!adapter.isEndlessScrollEnabled()) { - holder.progressMessage.setText(context.getString(R.string.endless_disabled)); - } else if (payloads.contains(Payload.NO_MORE_LOAD)) { - holder.progressMessage.setText(context.getString(R.string.no_more_load_retry)); - } + break; } } @@ -76,4 +110,12 @@ public void scrollAnimators(@NonNull List animators, int position, boo } } + 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/res/values/strings.xml b/flexible-adapter-app/src/main/res/values/strings.xml index c2cdc435..24143a37 100644 --- a/flexible-adapter-app/src/main/res/values/strings.xml +++ b/flexible-adapter-app/src/main/res/values/strings.xml @@ -47,7 +47,9 @@ 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. + No more items to load (max reached). + Cancelled by the user. + An error occurred while loading more items. Title 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 e5b0c994..63cadcca 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java @@ -367,16 +367,16 @@ protected final void animateView(final RecyclerView.ViewHolder holder, final int 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>LasVisPos=" + (position > lastVisiblePosition) : "") - + " mMaxChildViews=" + mMaxChildViews - ); - } +// if (DEBUG) { +// Log.v(TAG, "shouldAnimate=" + shouldAnimate +// + " isFastScroll=" + isFastScroll +// + " isNotified=" + mAnimatorNotifierObserver.isPositionNotified() +// + " isReverseEnabled=" + isReverseEnabled +// + " mLastAnimatedPosition=" + mLastAnimatedPosition +// + (!isReverseEnabled ? " Pos>LasVisPos=" + (position > lastVisiblePosition) : "") +// + " mMaxChildViews=" + mMaxChildViews +// ); +// } if (holder instanceof FlexibleViewHolder && shouldAnimate && !isFastScroll && !mAnimatorNotifierObserver.isPositionNotified() && (position > lastVisiblePosition || isReverseEnabled || isScrollableHeaderOrFooter(position) || (position == 0 && mMaxChildViews == 0))) { @@ -401,7 +401,7 @@ protected final void animateView(final RecyclerView.ViewHolder holder, final int duration = animator.getDuration(); } } - Log.v(TAG, "duration=" + duration); + //Log.v(TAG, "duration=" + duration); set.setDuration(duration > 0 ? duration : mDuration); set.addListener(new HelperAnimatorListener(hashCode)); if (mEntryStep) { @@ -692,8 +692,8 @@ public void clearNotified() { private void markNotified() { notified = !animateFromObserver; - if (DEBUG) - Log.v(TAG, "animateFromObserver=" + animateFromObserver + " notified=" + notified); +// if (DEBUG) +// Log.v(TAG, "animateFromObserver=" + animateFromObserver + " notified=" + notified); } @Override From e8c746905a0911244847c1655653fb6e354294ae Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Thu, 12 Jan 2017 00:22:00 +0100 Subject: [PATCH 88/92] Added methods count badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a3f4ef6..dd591a6a 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ [![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 ####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 2016.12.29 (159KB) +- **NEW!** First release candidate: [v5.0.0-rc1](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-rc1) built on 2016.12.29 - 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.
      From 61ab9e96ecccf4e857ff5e3454c93e9352dfd7a1 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Thu, 12 Jan 2017 00:23:45 +0100 Subject: [PATCH 89/92] Cleaned publishing task --- jfrog-bintray-publish.gradle | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/jfrog-bintray-publish.gradle b/jfrog-bintray-publish.gradle index cce64bde..05f801d0 100644 --- a/jfrog-bintray-publish.gradle +++ b/jfrog-bintray-publish.gradle @@ -2,30 +2,6 @@ apply plugin: 'com.jfrog.bintray' version = libraryVersion -task clean(type: Delete) { - delete rootProject.buildDir -} - -task sourcesJar(type: Jar) { - from android.sourceSets.main.java.srcDirs - classifier = 'sources' -} - -task javadoc(type: Javadoc) { - source = android.sourceSets.main.java.srcDirs - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.getDestinationDir() -} - -artifacts { - archives javadocJar - archives sourcesJar -} - def getRepositoryUsername() { return getProperties().get('bintray.user') } From 37a304be45652624a274003c738e5bb208ed653b Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Fri, 13 Jan 2017 22:41:13 +0100 Subject: [PATCH 90/92] Fixed #265 - UndoHelper dismissed quickly with SnackBar predefined lengths --- .../flexibleadapter/helpers/UndoHelper.java | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java index 566fc4d7..146e6af4 100644 --- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java +++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/helpers/UndoHelper.java @@ -73,8 +73,8 @@ public class UndoHelper extends Snackbar.Callback { /** * Default constructor. - *

      Only from version 5.0.0-b8, by calling this constructor, - * {@link FlexibleAdapter#setPermanentDelete(boolean)} is set {@code false} automatically. + *

      By calling this constructor, {@link FlexibleAdapter#setPermanentDelete(boolean)} + * is set {@code false} automatically. * * @param adapter the instance of {@code FlexibleAdapter} * @param undoListener the callback for the Undo and Delete confirmation @@ -111,26 +111,26 @@ public UndoHelper withAction(@Action int action, @NonNull OnActionListener actio return this; } - /** - * Sets the text color of the action. - * - * @param color the color for the action button - * @return this object, so it can be chained - */ - public UndoHelper withActionTextColor(@ColorInt int color) { - this.mActionTextColor = color; - return this; - } + /** + * Sets the text color of the action. + * + * @param color the color for the action button + * @return this object, so it can be chained + */ + public UndoHelper withActionTextColor(@ColorInt int color) { + this.mActionTextColor = color; + return this; + } /** * As {@link #remove(List, View, CharSequence, CharSequence, int)} but with String * resources instead of CharSequence. */ - public Snackbar remove(List positions, @NonNull View mainView, - @StringRes int messageStringResId, @StringRes int actionStringResId, - @IntRange(from = 0) int undoTime) { + public void remove(List positions, @NonNull View mainView, + @StringRes int messageStringResId, @StringRes int actionStringResId, + @IntRange(from = -1) int undoTime) { Context context = mainView.getContext(); - return remove(positions, mainView, context.getString(messageStringResId), + remove(positions, mainView, context.getString(messageStringResId), context.getString(actionStringResId), undoTime); } @@ -146,17 +146,16 @@ public Snackbar remove(List positions, @NonNull View mainView, * @param actionText the action text to display * @param undoTime How long to display the message. Either {@link Snackbar#LENGTH_SHORT} or * {@link Snackbar#LENGTH_LONG} or any custom Integer. - * @return The SnackBar instance to be customized again * @see #remove(List, View, int, int, int) */ @SuppressWarnings("WrongConstant") - public Snackbar remove(List positions, @NonNull View mainView, - CharSequence message, CharSequence actionText, - @IntRange(from = 0) int undoTime) { + public void remove(List positions, @NonNull View mainView, + CharSequence message, CharSequence actionText, + @IntRange(from = -1) int undoTime) { this.mPositions = positions; Snackbar snackbar; if (!mAdapter.isPermanentDelete()) { - snackbar = Snackbar.make(mainView, message, undoTime + 400)//More time due to the animation + snackbar = Snackbar.make(mainView, message, undoTime > 0 ? undoTime + 400 : undoTime) .setAction(actionText, new View.OnClickListener() { @Override public void onClick(View v) { @@ -170,9 +169,8 @@ public void onClick(View v) { if (mActionTextColor != Color.TRANSPARENT) { snackbar.setActionTextColor(mActionTextColor); } - snackbar.setCallback(this); + snackbar.addCallback(this); snackbar.show(); - return snackbar; } /** @@ -201,13 +199,13 @@ public void onDismissed(Snackbar snackbar, int event) { @Override public void onShown(Snackbar snackbar) { boolean consumed = false; - //Perform the action before deletion + // Perform the action before deletion if (mActionListener != null) consumed = mActionListener.onPreAction(); - //Remove selected items from Adapter list after SnackBar is shown + // Remove selected items from Adapter list after SnackBar is shown if (!consumed) mAdapter.removeItems(mPositions, mPayload); - //Perform the action after the deletion + // Perform the action after the deletion if (mActionListener != null) mActionListener.onPostAction(); - //Here, we can notify the callback only in case of permanent deletion + // Here, we can notify the callback only in case of permanent deletion if (mAdapter.isPermanentDelete() && mUndoListener != null) mUndoListener.onDeleteConfirmed(mAction); } @@ -261,7 +259,7 @@ public interface OnUndoListener { * Called when Undo timeout is over and action 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 FlexibleAdapter#getDeletedItems()} from the + * So, to get deleted items, use {@link FlexibleAdapter#getDeletedItems()} from the * implementation of this method.

      * * @param action one of {@link UndoHelper#ACTION_REMOVE}, {@link UndoHelper#ACTION_UPDATE} From a941b4c0b121a08143ec82b325e5a736bfd255c8 Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 15 Jan 2017 11:02:31 +0100 Subject: [PATCH 91/92] DemoApp --- .../fragments/FragmentEndlessScrolling.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) 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 adcec680..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 @@ -125,24 +125,24 @@ public void showNewLayoutInfo(MenuItem item) { 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()); -} + /** + * 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. From 9e2d0f8d699ad7c8c4c99798620f877329a2319b Mon Sep 17 00:00:00 2001 From: Davide Steduto Date: Sun, 15 Jan 2017 11:02:39 +0100 Subject: [PATCH 92/92] Update Readme for RC1 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dd591a6a..00cb0331 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ####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 2016.12.29 +- **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.
      @@ -19,7 +19,7 @@ Since I created this library, it has become easy to configure how views will be 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 features. +This library is configurable and it guides the developers to create a better user experience and now, even more with the new features. #### Main features * Simple item selection with ripple effect, Single & Multi selection mode. @@ -102,7 +102,7 @@ You can download the latest demo App from the latest release page OR run it with # Change Log ###### Latest release -[v5.0.0-rc1](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-rc1) - 2016.12.29 +[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 |