diff --git a/README.md b/README.md index 1997c4ab..c59b4515 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-FlexibleAdapter-green.svg?style=flat)](https://android-arsenal.com/details/1/2207) +[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-FlexibleAdapter-green.svg?style=flat)](https://android-arsenal.com/details/1/2207) [![Download](https://api.bintray.com/packages/davideas/maven/flexible-adapter/images/download.svg) ](https://bintray.com/davideas/maven/flexible-adapter/_latestVersion) # Flexible Adapter -###### A pattern for every RecyclerView - Master branch: v4 of 2015.10.18 +###### A pattern for every RecyclerView - Master branch: v4 of 2015.11.01 #### Main functionalities * Base item selection (but also SINGLE & MULTI selection mode) in the Recycler View with ripple effect. @@ -26,11 +26,15 @@ Finally note that, this adapter handles the basic clicks: _single_ and _long cli ![Search screen](/screenshots/search.png) ![Undo Screen](/screenshots/undo.png) #Setup -Ultra simple: -No needs to create and import a library for just 2 files, so just *copy* SelectableAdapter.java & FlexibleAdapter.java in your *common* package and start to *extend* FlexibleAdapter from your custom Adapter (see my ExampleAdapter). +Using JCenter repo (bintray.com) +``` +dependencies { + compile 'eu.davidea:flexible-adapter:4.0.1' +} +``` +Or you can just *copy* SelectableAdapter.java & FlexibleAdapter.java in your *common* package and start to *extend* FlexibleAdapter from your custom Adapter (see my ExampleAdapter). Remember to initialize `mItems` (List already included in FlexibleAdapter) in order to manage list items. Method `updateDataSet(..)` can help in this. - #### Pull requests / Issues / Improvement requests Feel free to contribute and ask! @@ -126,18 +130,23 @@ public class MainActivity extends AppCompatActivity { ``` # Change Log -###### v4 - 2015.10.18 -- Added **FilterAsyncTask** to asynchronously load the list (This might not work well and binding is excluded from Async) -- Enabled **Search** filter through _updateDataSet_ method (Note: as the example is made, the search regenerate the list!) +###### v4.0.1 - 2015.11.01 +- Refactored module names, package signatures and gradle build files. Code remains unchanged. +- Configuration for JCenter, now FlexibleAdapter is a lightweight standalone library! + **Note:** FastScroller and ItemAnimators are excluded from the library, but you can see them in the example App. + +###### v4.0 - 2015.10.18 +- Added **FilterAsyncTask** to asynchronously load the list (This might not work well and binding is excluded from Async). +- Enabled **Search** filter through _updateDataSet_ method (Note: as the example is made, the search regenerate the list!). - Included some ItemAnimators from https://github.com/wasabeef/recyclerview-animators - (in the example SlideInRightAnimator is used), but all need to be adapted if used with Support v23.1.0 + (in the example SlideInRightAnimator is used), but all need to be adapted if used with Support v23.1.0. - Included and customized **FastScroller** library from https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller My version has FrameLayout extended instead of LinearLayout, and a friendly layout. -- Added **SwipeRefreshLayout**, just an usage example -- Use of Handler instead of TimerTask while Undo -- _FlexibleAdapter_ can now return deleted items before they are removed from memory -- _SelectableAdapter.selectAll()_ can now skip selection on one specific ViewType -- Adapted MainActivity +- Added **SwipeRefreshLayout**, just an usage example. +- Use of Handler instead of TimerTask while Undo. +- _FlexibleAdapter_ can now return deleted items before they are removed from memory. +- _SelectableAdapter.selectAll()_ can now skip selection on one specific ViewType. +- Adapted MainActivity. ###### Old releases See [releases](https://github.com/davideas/FlexibleAdapter/releases) for old versions. diff --git a/bintray.gradle b/bintray.gradle new file mode 100644 index 00000000..c827d00b --- /dev/null +++ b/bintray.gradle @@ -0,0 +1,51 @@ +apply plugin: 'com.jfrog.bintray' + +version = libraryVersion + +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.destinationDir +} +artifacts { + archives javadocJar + archives sourcesJar +} + +// Bintray +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) + +bintray { + user = properties.getProperty("bintray.user") + key = properties.getProperty("bintray.apikey") + + configurations = ['archives'] + pkg { + repo = bintrayRepo + name = bintrayName + desc = libraryDescription + websiteUrl = siteUrl + vcsUrl = gitUrl + licenses = allLicenses + publish = true + publicDownloadNumbers = true + version { + desc = libraryDescription + gpg { + sign = true //Determines whether to GPG sign the files. The default is false + passphrase = properties.getProperty("bintray.gpg.password") + //Optional. The passphrase for GPG signing' + } + } + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index d2ee2a39..80ff6b4c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,55 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +ext { + //Author + developerId = 'davideas' + developerName = 'Davide Steduto' + developerEmail = 'dave.dna@gmail.com' + + //Library + libraryCode = 8 + libraryVersion = '4.0.1' + libraryDate = " of 2015.11.01" + libraryDescription = 'A pattern for every RecyclerView' + libraryName = 'FlexibleAdapter' + + //Library Repository + bintrayRepo = 'maven' + bintrayName = 'flexible-adapter' + publishedGroupId = 'eu.davidea' + artifact = bintrayName + siteUrl = 'https://github.com/davideas/FlexibleAdapter' + gitUrl = 'https://github.com/davideas/FlexibleAdapter.git' + + //Support and Build tools version + minSdk = 14 + targetSdk = 23 + buildTools = '23.0.1' + supportLibrary = '23.0.1' + + //Support Libraries dependencies + supportDependencies = [ + design : "com.android.support:design:${supportLibrary}", + recyclerview : "com.android.support:recyclerview-v7:${supportLibrary}", + cardview : "com.android.support:cardview-v7:${supportLibrary}", + appcompat : "com.android.support:appcompat-v7:${supportLibrary}", + customtabs : "com.android.support:customtabs:${supportLibrary}", + support : "com.android.support:support-v13:${supportLibrary}'", + annotations : "com.android.support:support-annotations:${supportLibrary}" + ] + + licenseName = 'The Apache Software License, Version 2.0' + licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + allLicenses = ["Apache-2.0"] +} buildscript { repositories { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' - + classpath 'com.android.tools.build:gradle:1.3.0' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.3' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -17,3 +60,7 @@ allprojects { jcenter() } } + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/flexible-adapter-app/build.gradle b/flexible-adapter-app/build.gradle new file mode 100644 index 00000000..1bdcb37f --- /dev/null +++ b/flexible-adapter-app/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion targetSdk + buildToolsVersion buildTools + + defaultConfig { + applicationId "eu.davidea.examples.flexibleadapter" + minSdkVersion minSdk + targetSdkVersion targetSdk + versionCode versionCode + versionName libraryVersion + libraryDate + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + //compile 'eu.davidea:flexible-adapter:4.0.1' + compile project (":flexible-adapter-lib") + compile supportDependencies.appcompat + compile (supportDependencies.design) { + exclude module: 'support-v4' + exclude module: 'support-annotations' + } + compile (supportDependencies.recyclerview) { + exclude module: 'support-v4'; + exclude module: 'support-annotations' + } +} \ No newline at end of file diff --git a/flexibleAdapter/src/main/AndroidManifest.xml b/flexible-adapter-app/src/main/AndroidManifest.xml similarity index 93% rename from flexibleAdapter/src/main/AndroidManifest.xml rename to flexible-adapter-app/src/main/AndroidManifest.xml index 698a474a..8f7ad161 100644 --- a/flexibleAdapter/src/main/AndroidManifest.xml +++ b/flexible-adapter-app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="eu.davidea.examples.flexibleadapter"> + tools:context="eu.davidea.examples.flexibleadapter.example.MainActivity"> - - + tools:context="eu.davidea.examples.flexibleadapter.example.MainActivity"> \ No newline at end of file diff --git a/flexible-adapter-lib/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java b/flexible-adapter-lib/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java new file mode 100644 index 00000000..1ec10d7b --- /dev/null +++ b/flexible-adapter-lib/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java @@ -0,0 +1,401 @@ +package eu.davidea.flexibleadapter; + +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +/** + * This class provides a set of standard methods to handle changes on the data set + * such as adding, removing, moving an item. + * + * Remember to call {@link RecyclerView#scheduleLayoutAnimation()} after adding or + * removing an item from the Adapter when the activity is paused. + * + * VH is your implementation of {@link RecyclerView.ViewHolder}. + * T is your domain object containing the data. + * + * @author Davide Steduto + */ +public abstract class FlexibleAdapter extends SelectableAdapter { + + private static final String TAG = FlexibleAdapter.class.getSimpleName(); + public static final long UNDO_TIMEOUT = 5000L; + + public interface OnUpdateListener { + void onLoadComplete(); + //void onProgressUpdate(int progress); + } + + /** + * Lock used to modify the content of {@link #mItems}. Any write operation performed on the array should be + * synchronized on this lock. + */ + private final Object mLock = new Object(); + + protected List mItems; + protected List mDeletedItems; + protected List mOriginalPosition; + //Searchable fields + protected static String mSearchText; //Static: It can exist only 1 searchText + protected OnUpdateListener mUpdateListener; + protected Handler mHandler; + + public FlexibleAdapter() { + } + + /** + * Constructor for Asynchronous loading.
+ * Experimental: not working very well, it might be slow. + * + * @param listener {@link OnUpdateListener} + */ + public FlexibleAdapter(Object listener) { + if (listener instanceof OnUpdateListener) + this.mUpdateListener = (OnUpdateListener) listener; + else + Log.w(TAG, "Listener is not an instance of OnUpdateListener!"); + } + + /** + * Convenience method to call {@link #updateDataSet(String)} with {@link null} as param. + */ + public void updateDataSet() { + updateDataSet(null); + }; + /** + * This method will refresh the entire DataSet content.
+ * The parameter is useful to filter the DataSet. It can be removed + * or the type can be changed accordingly (String is the most used value).
+ * Pass null value in case not used. + * + * @param param A custom parameter to filter the DataSet + */ + public abstract void updateDataSet(String param); + + /** + * This method execute {@link #updateDataSet(String)} asynchronously + * with {@link FilterAsyncTask}.

+ * Note: {@link #notifyDataSetChanged()} is automatically called at the end of the process. + * + * @param param A custom parameter to filter the DataSet + */ + public void updateDataSetAsync(String param) { + if (mUpdateListener == null) { + Log.w(TAG, "OnUpdateListener is not initialized. UpdateDataSet is not using FilterAsyncTask!"); + updateDataSet(param); + return; + } + new FilterAsyncTask().execute(param); + } + + /** + * Returns the custom object "Item". + * + * @param position The position of the item in the list + * @return The custom "Item" object or null if item not found + */ + public T getItem(int position) { + if (position < 0 || position >= mItems.size()) return null; + return mItems.get(position); + } + + /** + * Retrieve the position of the Item in the Adapter + * + * @param item The item + * @return The position in the Adapter if found, -1 otherwise + */ + public int getPositionForItem(T item) { + return mItems != null && mItems.size() > 0 ? mItems.indexOf(item) : -1; + } + + public boolean contains(T item) { + return mItems != null && mItems.contains(item); + } + + @Override + public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); + + @Override + public abstract void onBindViewHolder(VH holder, final int position); + + @Override + public int getItemCount() { + return mItems != null ? mItems.size() : 0; + } + + public void updateItem(int position, T item) { + if (position < 0) return; + synchronized (mLock) { + mItems.set(position, item); + } + Log.d(TAG, "updateItem notifyItemChanged on position "+position); + notifyItemChanged(position); + } + + /** + * Insert given Item at position or Add Item at last position. + * + * @param position Position of the item to add + * @param item The item to add + */ + public void addItem(int position, T item) { + if (position < 0) return; + + //Insert Item + if (position < mItems.size()) { + Log.d(TAG, "addItem notifyItemInserted on position " + position); + synchronized (mLock) { + mItems.add(position, item); + } + + //Add Item at the last position + } else { + Log.d(TAG, "addItem notifyItemInserted on last position"); + synchronized (mLock) { + mItems.add(item); + position = mItems.size(); + } + } + + notifyItemInserted(position); + } + + /* DELETE ITEMS METHODS */ + + /** + * The item is retained in a list for an eventual Undo. + * + * @param position The position of item to remove + * @see #startUndoTimer() + * @see #restoreDeletedItems() + * @see #emptyBin() + */ + public void removeItem(int position) { + if (position < 0) return; + if (position < mItems.size()) { + Log.d(TAG, "removeItem notifyItemRemoved on position " + position); + synchronized (mLock) { + saveDeletedItem(position, mItems.remove(position)); + } + notifyItemRemoved(position); + } else { + Log.w(TAG, "removeItem WARNING! Position OutOfBound! Review the position to remove!"); + } + } + + /** + * Every item is retained in a list for an eventual Undo. + * + * @param selectedPositions List of item positions to remove + * @see #startUndoTimer() + * @see #restoreDeletedItems() + * @see #emptyBin() + */ + public void removeItems(List selectedPositions) { + Log.d(TAG, "removeItems reverse Sorting positions --------------"); + // Reverse-sort the list + Collections.sort(selectedPositions, new Comparator() { + @Override + public int compare(Integer lhs, Integer rhs) { + return rhs - lhs; + } + }); + + // Split the list in ranges + while (!selectedPositions.isEmpty()) { + if (selectedPositions.size() == 1) { + removeItem(selectedPositions.get(0)); + //Align the selection list when removing the item + selectedPositions.remove(0); + } else { + int count = 1; + while (selectedPositions.size() > count && selectedPositions.get(count).equals(selectedPositions.get(count - 1) - 1)) { + ++count; + } + + if (count == 1) { + removeItem(selectedPositions.get(0)); + } else { + removeRange(selectedPositions.get(count - 1), count); + } + + for (int i = 0; i < count; ++i) { + selectedPositions.remove(0); + } + } + Log.d(TAG, "removeItems current selection " + getSelectedItems()); + } + } + + private void removeRange(int positionStart, int itemCount) { + Log.d(TAG, "removeRange positionStart="+positionStart+ " itemCount="+itemCount); + for (int i = 0; i < itemCount; ++i) { + synchronized (mLock) { + saveDeletedItem(positionStart, mItems.remove(positionStart)); + } + } + Log.d(TAG, "removeRange notifyItemRangeRemoved"); + notifyItemRangeRemoved(positionStart, itemCount); + } + + /* UNDO METHODS */ + + /** + * Save temporary Items for an eventual Undo. + * + * @param position The position of the item to retain. + */ + public void saveDeletedItem(int position, T item) { + if (mDeletedItems == null) { + mDeletedItems = new ArrayList(); + mOriginalPosition = new ArrayList(); + } + Log.d(TAG, "Recycled "+getItem(position)+" on position="+position); + mDeletedItems.add(item); + mOriginalPosition.add(position); + } + + /** + * @return The list of deleted items + */ + public List getDeletedItems() { + return mDeletedItems; + } + + public boolean isRestoreInTime() { + return mDeletedItems != null && mDeletedItems.size() > 0; + } + + /** + * Restore items just removed. + */ + public void restoreDeletedItems() { + stopUndoTimer(); + //Reverse insert (list was reverse ordered on Delete) + for (int i = mOriginalPosition.size()-1; i >= 0; i--) { + addItem(mOriginalPosition.get(i), mDeletedItems.get(i)); + } + emptyBin(); + } + + /** + * Clean memory from items just removed.
+ * Note: This method is automatically called after timer is over and after a restoration. + */ + public void emptyBin() { + if (mDeletedItems != null) { + mDeletedItems.clear(); + mOriginalPosition.clear(); + } + } + + /** + * Convenience method to start Undo timer with default timeout of 5'' + */ + public void startUndoTimer() { + startUndoTimer(0); + } + + /** + * Start Undo timer with custom timeout + * + * @param timeout Custom timeout + */ + public void startUndoTimer(long timeout) { + mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { + public boolean handleMessage(Message message) { + emptyBin(); + return true; + } + }); + mHandler.sendMessageDelayed(Message.obtain(mHandler), timeout > 0 ? timeout : UNDO_TIMEOUT); + } + + /** + * Stop Undo timer. + *
Note: This method is automatically called in case of restoration. + */ + private void stopUndoTimer() { + if (mHandler != null) { + mHandler.removeCallbacksAndMessages(null); + mHandler = null; + } + } + + public static boolean hasSearchText() { + return mSearchText != null && mSearchText.length() > 0; + } + + public static String getSearchText() { + return mSearchText; + } + + public static void setSearchText(String searchText) { + if (searchText != null) + mSearchText = searchText.trim().toLowerCase(Locale.getDefault()); + else mSearchText = ""; + } + + /** + * DEFAULT IMPLEMENTATION, OVERRIDE TO HAVE OWN FILTER! + * + *

+ * Performs filtering on the provided object and returns true, if the object should be in the filtered collection, + * or false if it shouldn't. + * + * @param myObject The object to be inspected + * @param constraint Constraint, that the object has to fulfil + * @return true, if the object should be in the filteredResult, false otherwise + */ + protected boolean filterObject(T myObject, String constraint) { + final String valueText = myObject.toString().toLowerCase(); + + //First match against the whole, non-splitted value + if (valueText.startsWith(constraint)) { + return true; + } else { + final String[] words = valueText.split(" "); + + //Start at index 0, in case valueText starts with space(s) + for (String word : words) { + if (word.startsWith(constraint)) { + return true; + } + } + } + //No match, so don't add to collection + return false; + } + + public class FilterAsyncTask extends AsyncTask { + + private final String TAG = FilterAsyncTask.class.getSimpleName(); + + @Override + protected Void doInBackground(String... params) { + Log.i(TAG, "doInBackground - started FilterAsyncTask!"); + updateDataSet(params[0]); + Log.i(TAG, "doInBackground - ended FilterAsyncTask!"); + return null; + } + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + mUpdateListener.onLoadComplete(); + notifyDataSetChanged(); + } + } + +} \ No newline at end of file diff --git a/flexible-adapter-lib/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java b/flexible-adapter-lib/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java new file mode 100644 index 00000000..063baad7 --- /dev/null +++ b/flexible-adapter-lib/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java @@ -0,0 +1,236 @@ +package eu.davidea.flexibleadapter; + +import android.os.Bundle; +import android.support.v7.widget.RecyclerView; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * This class provides a set of standard methods to handle the selection on the items of an Adapter. + * + * @author Davide Steduto + */ +public abstract class SelectableAdapter extends RecyclerView.Adapter { + + private static final String TAG = SelectableAdapter.class.getSimpleName(); + /** + * Default mode for selection + */ + public static final int MODE_SINGLE = 1; + /** + * Multi selection will be activated + */ + public static final int MODE_MULTI = 2; + + private ArrayList selectedItems; + private int mode; + + public SelectableAdapter() { + this.selectedItems = new ArrayList(); + this.mode = MODE_SINGLE; + } + + /** + * Set the mode of the selection, MODE_SINGLE is the default: + *
    + *
  • if {@link #MODE_SINGLE}, it will switch the selection position (previous selection is cleared automatically); + *
  • if {@link #MODE_MULTI}, it will add the position to the list of the items selected. + *
+ * NOTE: #mModeMultiJustFinished is set true when #MODE_MULTI is finished. + * + * @param mode MODE_SINGLE or MODE_MULTI + */ + public void setMode(int mode) { + this.mode = mode; + } + + /** + * The current selection mode of the Adapter. + * + * @return current mode + * @see #MODE_SINGLE + * @see #MODE_MULTI + */ + public int getMode() { + return mode; + } + + /** + * Indicates if the item at position position is selected. + * + * @param position Position of the item to check. + * @return true if the item is selected, false otherwise. + */ + public boolean isSelected(int position) { + return selectedItems.contains(Integer.valueOf(position)); + } + + /** + * Convenience method to never invalidate the Item. + * + * @param position Position of the item to toggle the selection status for. + * @see #toggleSelection(int, boolean) + */ + public void toggleSelection(int position) { + toggleSelection(position, false); + } + + /** + * Toggle the selection status of the item at a given position.
+ * The behaviour depends on the selection mode previously set with {@link #setMode}. + * + *

+ * Optionally the item can be invalidated.
+ * However it is preferable to set false and to handle the Activated/Selected State of + * the ItemView in the Click events of the ViewHolder after the selection is registered and + * up to date: Very Useful if the item has views with own animation to perform! + * + *

+ * Usage: + *
    + *
  • If you don't want any item to be selected/activated at all, just don't call this method.
  • + *
  • To have actually the item visually selected you need to add a custom Selector Drawable to your layout/view of the Item. + * It's preferable: android:background="?attr/selectableItemBackground" in your layout, pointing to a custom Drawable in the style.xml + * (note: prefix ?android:attr seems to not work).
  • + *
  • In onClick, enable the Activated/Selected State of the ItemView of the ViewHolder after the listener consumed the event: + * itemView.setActivated(mAdapter.isSelected(getAdapterPosition()));
  • + *
  • In onBindViewHolder, adjust the selection status: holder.itemView.setActivated(isSelected(position));
  • + *
  • If invalidate is set true, {@link #notifyItemChanged} is called and {@link #onBindViewHolder} will be automatically called + * afterwards overriding any animation in the ItemView!
  • + *
T + * + * @param position Position of the item to toggle the selection status for. + * @param invalidate Boolean to indicate if the row must be invalidated and item rebinded. + */ + public void toggleSelection(int position, boolean invalidate) { + if (position < 0) return; + if (mode == MODE_SINGLE) clearSelection(); + + int index = selectedItems.indexOf(position); + if (index != -1) { + Log.d(TAG, "toggleSelection removing selection on position "+position); + selectedItems.remove(index); + } else { + Log.d(TAG, "toggleSelection adding selection on position "+position); + selectedItems.add(position); + } + if (invalidate) { + Log.d(TAG, "toggleSelection notifyItemChanged on position "+position); + notifyItemChanged(position); + } + Log.d(TAG, "toggleSelection current selection " + selectedItems); + } + + /** + * Deprecated! Reasons: + *
- Use {@link #toggleSelection} for normal situation instead. + *
- Use {@link #getSelectedItems}.iterator() to avoid java.util.ConcurrentModificationException. + * + *

+ * This method is used only after a removal of an Item and useful only in certain + * situations such when not all selected Items can be removed (business exceptions dependencies) + * while others still remains selected. + *
For normal situations use {@link #toggleSelection} instead. + * + *

+ * Remove the selection if at the specified + * position the item was previously selected. + * + *

+ * Note: notifyItemChanged on the position is NOT called to avoid double call + * when removeItems! + * + * @param position + */ + @Deprecated + protected void removeSelection(int position) { + if (position < 0) return; + Log.d(TAG, "removeSelection on position "+position); + int index = selectedItems.indexOf(Integer.valueOf(position)); + if (index != -1) selectedItems.remove(index); + //Avoid double notification: + //Usually the notification is made in FlexibleAdapter.removeItem(); + //notifyItemChanged(position); + } + + /** + * Convenience method when there is nothing to skip. + */ + public void selectAll() { + selectAll(-1); + } + /** + * Add the selection status for all items. + * The selector container is sequentially filled with All items positions. + *
Note: All items are invalidated and rebinded! + * + * @param skipViewType ViewType for which we don't want selection + */ + public void selectAll(int skipViewType) { + Log.d(TAG, "selectAll"); + selectedItems = new ArrayList(getItemCount()); + for (int i = 0; i < getItemCount(); i++) { + if (getItemViewType(i) == skipViewType) continue; + selectedItems.add(i); + Log.d(TAG, "selectAll notifyItemChanged on position "+i); + notifyItemChanged(i); + } + } + + /** + * Clear the selection status for all items one by one to not kill animations in the items. + *

+ * Note 1: Items are invalidated and rebinded!
+ * Note 2: This method use java.util.Iterator to avoid java.util.ConcurrentModificationException. + */ + public void clearSelection() { + Iterator iterator = selectedItems.iterator(); + while (iterator.hasNext()) { + //The notification is done only on items that are currently selected. + int i = iterator.next(); + iterator.remove(); + Log.d(TAG, "clearSelection notifyItemChanged on position "+i); + notifyItemChanged(i); + } + } + + /** + * Count the selected items. + * + * @return Selected items count + */ + public int getSelectedItemCount() { + return selectedItems.size(); + } + + /** + * Indicates the list of selected items. + * + * @return List of selected items ids + */ + public List getSelectedItems() { + return selectedItems; + } + + /** + * Save the state of the current selection on the items. + * + * @param outState + */ + public void onSaveInstanceState(Bundle outState) { + outState.putIntegerArrayList(TAG, selectedItems); + } + + /** + * Restore the previous state of the selection on the items. + * + * @param savedInstanceState + */ + public void onRestoreInstanceState(Bundle savedInstanceState) { + selectedItems = savedInstanceState.getIntegerArrayList(TAG); + } + +} \ No newline at end of file diff --git a/flexibleAdapter/build.gradle b/flexibleAdapter/build.gradle deleted file mode 100644 index 13bbbee9..00000000 --- a/flexibleAdapter/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" - - defaultConfig { - applicationId "eu.davidea.flexibleadapter" - minSdkVersion 14 - targetSdkVersion 23 - versionCode 7 - versionName "4.0 of 2015.10.18" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - compile ('com.android.support:appcompat-v7:23.0.1') { - - } - compile ('com.android.support:recyclerview-v7:23.0.1') { - exclude module: 'support-v4'; - exclude module: 'support-annotations' - } - compile ('com.android.support:design:23.0.1') { - exclude module: 'support-v4' - exclude module: 'support-annotations' - } -} \ No newline at end of file diff --git a/flexibleAdapter/src/main/ic_launcher-web.png b/flexibleAdapter/src/main/ic_launcher-web.png deleted file mode 100644 index becdf812..00000000 Binary files a/flexibleAdapter/src/main/ic_launcher-web.png and /dev/null differ diff --git a/flexibleAdapter/src/main/res/mipmap-hdpi/ic_launcher.png b/flexibleAdapter/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 88a339a2..00000000 Binary files a/flexibleAdapter/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/flexibleAdapter/src/main/res/mipmap-mdpi/ic_launcher.png b/flexibleAdapter/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c8bc1d34..00000000 Binary files a/flexibleAdapter/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/flexibleAdapter/src/main/res/mipmap-xhdpi/ic_launcher.png b/flexibleAdapter/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 03b48293..00000000 Binary files a/flexibleAdapter/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/flexibleAdapter/src/main/res/mipmap-xxhdpi/ic_launcher.png b/flexibleAdapter/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 30aab9ae..00000000 Binary files a/flexibleAdapter/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/flexibleAdapter/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/flexibleAdapter/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a0ff0d29..00000000 Binary files a/flexibleAdapter/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/install.gradle b/install.gradle new file mode 100644 index 00000000..980eba54 --- /dev/null +++ b/install.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.github.dcendents.android-maven' + +group = publishedGroupId // Maven Group ID for the artifact + +install { + repositories.mavenInstaller { + // This generates POM.xml with proper parameters + pom { + project { + packaging 'aar' + groupId publishedGroupId + artifactId artifact + + // Add your description here + name libraryName + description libraryDescription + url siteUrl + + // Set your license + licenses { + license { + name licenseName + url licenseUrl + } + } + developers { + developer { + id developerId + name developerName + email developerEmail + } + } + scm { + connection gitUrl + developerConnection gitUrl + url siteUrl + } + } + } + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 8bec98de..ebcc6e83 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':flexibleAdapter' +include ':flexible-adapter-app', ':flexible-adapter-lib'