diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c21fa8d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# built application files
+*.apk
+*.ap_
+
+# files for the dex VM
+*.dex
+
+# Java class files
+*.class
+
+# generated files
+bin/
+gen/
+
+*.properties
+
+# Eclipse project files
+.classpath
+.project
+
+.idea/
+build/
+*.iml
+
+dynamicgrid/proguard-project.txt
+
+dynamicgrid/build.xml
+
+dynamicgrid/ant.properties
+
+example/build.xml
+
+example/proguard-project.txt
+
+example/modules/
diff --git a/.gradle/4.1/fileChanges/last-build.bin b/.gradle/4.1/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/.gradle/4.1/fileChanges/last-build.bin
Binary files differ
diff --git a/.gradle/4.1/fileHashes/fileHashes.bin b/.gradle/4.1/fileHashes/fileHashes.bin
new file mode 100644
index 0000000..f76ada8
--- /dev/null
+++ b/.gradle/4.1/fileHashes/fileHashes.bin
Binary files differ
diff --git a/.gradle/4.1/fileHashes/fileHashes.lock b/.gradle/4.1/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..c124f3f
--- /dev/null
+++ b/.gradle/4.1/fileHashes/fileHashes.lock
Binary files differ
diff --git a/.gradle/4.10/fileChanges/last-build.bin b/.gradle/4.10/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/.gradle/4.10/fileChanges/last-build.bin
Binary files differ
diff --git a/.gradle/4.10/fileHashes/fileHashes.bin b/.gradle/4.10/fileHashes/fileHashes.bin
new file mode 100644
index 0000000..7be04a8
--- /dev/null
+++ b/.gradle/4.10/fileHashes/fileHashes.bin
Binary files differ
diff --git a/.gradle/4.10/fileHashes/fileHashes.lock b/.gradle/4.10/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..c14d06e
--- /dev/null
+++ b/.gradle/4.10/fileHashes/fileHashes.lock
Binary files differ
diff --git a/.gradle/4.4/fileChanges/last-build.bin b/.gradle/4.4/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/.gradle/4.4/fileChanges/last-build.bin
Binary files differ
diff --git a/.gradle/4.4/fileHashes/fileHashes.bin b/.gradle/4.4/fileHashes/fileHashes.bin
new file mode 100644
index 0000000..ec0798f
--- /dev/null
+++ b/.gradle/4.4/fileHashes/fileHashes.bin
Binary files differ
diff --git a/.gradle/4.4/fileHashes/fileHashes.lock b/.gradle/4.4/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..cc3a300
--- /dev/null
+++ b/.gradle/4.4/fileHashes/fileHashes.lock
Binary files differ
diff --git a/.gradle/5.1.1/executionHistory/executionHistory.bin b/.gradle/5.1.1/executionHistory/executionHistory.bin
new file mode 100644
index 0000000..d5efa78
--- /dev/null
+++ b/.gradle/5.1.1/executionHistory/executionHistory.bin
Binary files differ
diff --git a/.gradle/5.1.1/executionHistory/executionHistory.lock b/.gradle/5.1.1/executionHistory/executionHistory.lock
new file mode 100644
index 0000000..39d8e9f
--- /dev/null
+++ b/.gradle/5.1.1/executionHistory/executionHistory.lock
Binary files differ
diff --git a/.gradle/5.1.1/fileChanges/last-build.bin b/.gradle/5.1.1/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/.gradle/5.1.1/fileChanges/last-build.bin
Binary files differ
diff --git a/.gradle/5.1.1/fileContent/fileContent.lock b/.gradle/5.1.1/fileContent/fileContent.lock
new file mode 100644
index 0000000..9abf1c8
--- /dev/null
+++ b/.gradle/5.1.1/fileContent/fileContent.lock
Binary files differ
diff --git a/.gradle/5.1.1/fileHashes/fileHashes.bin b/.gradle/5.1.1/fileHashes/fileHashes.bin
new file mode 100644
index 0000000..995400c
--- /dev/null
+++ b/.gradle/5.1.1/fileHashes/fileHashes.bin
Binary files differ
diff --git a/.gradle/5.1.1/fileHashes/fileHashes.lock b/.gradle/5.1.1/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..a1dfe49
--- /dev/null
+++ b/.gradle/5.1.1/fileHashes/fileHashes.lock
Binary files differ
diff --git a/.gradle/5.1.1/fileHashes/resourceHashesCache.bin b/.gradle/5.1.1/fileHashes/resourceHashesCache.bin
new file mode 100644
index 0000000..55ca3b9
--- /dev/null
+++ b/.gradle/5.1.1/fileHashes/resourceHashesCache.bin
Binary files differ
diff --git a/.gradle/5.1.1/javaCompile/classAnalysis.bin b/.gradle/5.1.1/javaCompile/classAnalysis.bin
new file mode 100644
index 0000000..bc14cb7
--- /dev/null
+++ b/.gradle/5.1.1/javaCompile/classAnalysis.bin
Binary files differ
diff --git a/.gradle/5.1.1/javaCompile/jarAnalysis.bin b/.gradle/5.1.1/javaCompile/jarAnalysis.bin
new file mode 100644
index 0000000..ca880f5
--- /dev/null
+++ b/.gradle/5.1.1/javaCompile/jarAnalysis.bin
Binary files differ
diff --git a/.gradle/5.1.1/javaCompile/javaCompile.lock b/.gradle/5.1.1/javaCompile/javaCompile.lock
new file mode 100644
index 0000000..d117e24
--- /dev/null
+++ b/.gradle/5.1.1/javaCompile/javaCompile.lock
Binary files differ
diff --git a/.gradle/5.1.1/javaCompile/taskHistory.bin b/.gradle/5.1.1/javaCompile/taskHistory.bin
new file mode 100644
index 0000000..66d7151
--- /dev/null
+++ b/.gradle/5.1.1/javaCompile/taskHistory.bin
Binary files differ
diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock
new file mode 100644
index 0000000..f437ebb
--- /dev/null
+++ b/.gradle/buildOutputCleanup/buildOutputCleanup.lock
Binary files differ
diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin
new file mode 100644
index 0000000..b62837f
--- /dev/null
+++ b/.gradle/buildOutputCleanup/outputFiles.bin
Binary files differ
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37ec93a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..77731d9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,69 @@
+DynamicGrid
+===========
+
+Drag and drop GridView for Android.
+
+Depricated
+===========
+It's much better to use solutions based on recycler view. For example https://github.com/h6ah4i/android-advancedrecyclerview
+
+Demo
+----
+
+
+
+Requirements
+----------
+Rearranging items require api 8 (Froyo).
+All grid item animations require api 11 (Honeycomb).
+
+Usage
+----------
+All the same as for normal GridView. Adapter must extends
+[AbstractDynamicGridAdapter](https://github.com/askerov/DynamicGrid/blob/master/dynamicgrid/src/org/askerov/dynamicgid/AbstractDynamicGridAdapter.java "AbstractDynamicGridAdapter")
+or [BaseDynamicGridAdapter](https://github.com/askerov/DynamicGrid/blob/master/dynamicgrid/src/org/askerov/dynamicgid/BaseDynamicGridAdapter.java "BaseDynamicGridAdapter")
+
+```java
+gridView = (DynamicGridView) findViewById(R.id.dynamic_grid);
+// pass to adapter context, list of items and number of columns count
+gridView.setAdapter(new MyDynamicGridAdapter(this, itemsList, 3));
+```
+
+To start Drag'n'drop mode:
+
+```java
+gridView.startEditMode();
+```
+
+Or from onItemClik() and onItemLongClick()
+
+```java
+gridView.startEditMode(position);
+```
+
+To stop:
+
+```java
+gridView.stopEditMode();
+```
+
+Adding drop listener:
+
+```java
+gridView.setOnDropListener(new DynamicGridView.OnDropListener(){
+ @Override
+ public void onActionDrop(){
+ // stop edit mode immediately after drop item
+ gridView.stopEditMode();
+ }
+ });
+```
+
+You can find more detailed usage example [here](https://github.com/askerov/DynamicGrid/tree/master/example).
+
+Credits
+--------
+DynamicGridView based on [Daniel Olshansky](https://plus.google.com/108153578400873445224/) ListView cell dragging and rearranging [example](https://www.youtube.com/watch?v=_BZIvjMgH-Q).
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..8318500
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,24 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.4.0'
+ }
+}
+
+allprojects {
+ repositories {
+ mavenCentral()
+ maven {
+ url 'https://maven.google.com/'
+ name 'Google'
+ }
+ jcenter()
+ google()
+ }
+}
\ No newline at end of file
diff --git a/dynamicgrid/AndroidManifest.xml b/dynamicgrid/AndroidManifest.xml
new file mode 100644
index 0000000..0eaebcc
--- /dev/null
+++ b/dynamicgrid/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/dynamicgrid/build.gradle b/dynamicgrid/build.gradle
new file mode 100644
index 0000000..15877a3
--- /dev/null
+++ b/dynamicgrid/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 28
+ }
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = ['src']
+ resources.srcDirs = ['src']
+ aidl.srcDirs = ['src']
+ renderscript.srcDirs = ['src']
+ res.srcDirs = ['res']
+ assets.srcDirs = ['assets']
+ }
+ }
+}
\ No newline at end of file
diff --git a/dynamicgrid/res/values/dimens.xml b/dynamicgrid/res/values/dimens.xml
new file mode 100644
index 0000000..61729a8
--- /dev/null
+++ b/dynamicgrid/res/values/dimens.xml
@@ -0,0 +1,4 @@
+
+
+ 16dp
+
\ No newline at end of file
diff --git a/dynamicgrid/res/values/id.xml b/dynamicgrid/res/values/id.xml
new file mode 100644
index 0000000..5935269
--- /dev/null
+++ b/dynamicgrid/res/values/id.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/dynamicgrid/src/org/askerov/dynamicgrid/AbstractDynamicGridAdapter.java b/dynamicgrid/src/org/askerov/dynamicgrid/AbstractDynamicGridAdapter.java
new file mode 100644
index 0000000..7075780
--- /dev/null
+++ b/dynamicgrid/src/org/askerov/dynamicgrid/AbstractDynamicGridAdapter.java
@@ -0,0 +1,88 @@
+package org.askerov.dynamicgrid;
+
+import android.widget.BaseAdapter;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Author: alex askerov
+ * Date: 9/6/13
+ * Time: 7:43 PM
+ */
+
+
+/**
+ * Abstract adapter for {@link org.askerov.dynamicgrid.DynamicGridView} with sable items id;
+ */
+
+public abstract class AbstractDynamicGridAdapter extends BaseAdapter implements DynamicGridAdapterInterface {
+ public static final int INVALID_ID = -1;
+
+ private int nextStableId = 0;
+
+ private HashMap mIdMap = new HashMap();
+
+ /**
+ * Adapter must have stable id
+ *
+ * @return
+ */
+ @Override
+ public final boolean hasStableIds() {
+ return true;
+ }
+
+ /**
+ * creates stable id for object
+ *
+ * @param item
+ */
+ protected void addStableId(Object item) {
+ mIdMap.put(item, nextStableId++);
+ }
+
+ /**
+ * create stable ids for list
+ *
+ * @param items
+ */
+ protected void addAllStableId(List> items) {
+ for (Object item : items) {
+ addStableId(item);
+ }
+ }
+
+ /**
+ * get id for position
+ *
+ * @param position
+ * @return
+ */
+ @Override
+ public final long getItemId(int position) {
+ if (position < 0 || position >= mIdMap.size()) {
+ return INVALID_ID;
+ }
+ Object item = getItem(position);
+ return mIdMap.get(item);
+ }
+
+ /**
+ * clear stable id map
+ * should called when clear adapter data;
+ */
+ protected void clearStableIdMap() {
+ mIdMap.clear();
+ }
+
+ /**
+ * remove stable id for item. Should called on remove data item from adapter
+ *
+ * @param item
+ */
+ protected void removeStableID(Object item) {
+ mIdMap.remove(item);
+ }
+
+}
diff --git a/dynamicgrid/src/org/askerov/dynamicgrid/BaseDynamicGridAdapter.java b/dynamicgrid/src/org/askerov/dynamicgrid/BaseDynamicGridAdapter.java
new file mode 100644
index 0000000..167102a
--- /dev/null
+++ b/dynamicgrid/src/org/askerov/dynamicgrid/BaseDynamicGridAdapter.java
@@ -0,0 +1,114 @@
+package org.askerov.dynamicgrid;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Author: alex askerov
+ * Date: 9/7/13
+ * Time: 10:49 PM
+ */
+public abstract class BaseDynamicGridAdapter extends AbstractDynamicGridAdapter {
+ private Context mContext;
+
+ private ArrayList mItems = new ArrayList();
+ private int mColumnCount;
+
+ protected BaseDynamicGridAdapter(Context context, int columnCount) {
+ this.mContext = context;
+ this.mColumnCount = columnCount;
+ }
+
+ public BaseDynamicGridAdapter(Context context, List> items, int columnCount) {
+ mContext = context;
+ mColumnCount = columnCount;
+ init(items);
+ }
+
+ private void init(List> items) {
+ addAllStableId(items);
+ this.mItems.addAll(items);
+ }
+
+
+ public void set(List> items) {
+ clear();
+ init(items);
+ notifyDataSetChanged();
+ }
+
+ public void clear() {
+ clearStableIdMap();
+ mItems.clear();
+ notifyDataSetChanged();
+ }
+
+ public void add(Object item) {
+ addStableId(item);
+ mItems.add(item);
+ notifyDataSetChanged();
+ }
+
+ public void add(int position, Object item) {
+ addStableId(item);
+ mItems.add(position, item);
+ notifyDataSetChanged();
+ }
+
+ public void add(List> items) {
+ addAllStableId(items);
+ this.mItems.addAll(items);
+ notifyDataSetChanged();
+ }
+
+
+ public void remove(Object item) {
+ mItems.remove(item);
+ removeStableID(item);
+ notifyDataSetChanged();
+ }
+
+
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public int getColumnCount() {
+ return mColumnCount;
+ }
+
+ public void setColumnCount(int columnCount) {
+ this.mColumnCount = columnCount;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void reorderItems(int originalPosition, int newPosition) {
+ if (newPosition < getCount()) {
+ DynamicGridUtils.reorder(mItems, originalPosition, newPosition);
+ notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public boolean canReorder(int position) {
+ return true;
+ }
+
+ public List getItems() {
+ return mItems;
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+}
diff --git a/dynamicgrid/src/org/askerov/dynamicgrid/DynamicGridAdapterInterface.java b/dynamicgrid/src/org/askerov/dynamicgrid/DynamicGridAdapterInterface.java
new file mode 100644
index 0000000..76d0579
--- /dev/null
+++ b/dynamicgrid/src/org/askerov/dynamicgrid/DynamicGridAdapterInterface.java
@@ -0,0 +1,33 @@
+package org.askerov.dynamicgrid;
+
+/**
+ * Author: alex askerov
+ * Date: 18/07/14
+ * Time: 23:44
+ */
+
+/**
+ * Any adapter used with DynamicGridView must implement DynamicGridAdapterInterface.
+ * Adapter implementation also must has stable items id.
+ * See {@link org.askerov.dynamicgrid.AbstractDynamicGridAdapter} for stable id implementation example.
+ */
+
+public interface DynamicGridAdapterInterface {
+
+ /**
+ * Determines how to reorder items dragged from originalPosition to newPosition
+ */
+ void reorderItems(int originalPosition, int newPosition);
+
+ /**
+ * @return return columns number for GridView. Need for compatibility
+ * (@link android.widget.GridView#getNumColumns() requires api 11)
+ */
+ int getColumnCount();
+
+ /**
+ * Determines whether the item in the specified position can be reordered.
+ */
+ boolean canReorder(int position);
+
+}
diff --git a/dynamicgrid/src/org/askerov/dynamicgrid/DynamicGridUtils.java b/dynamicgrid/src/org/askerov/dynamicgrid/DynamicGridUtils.java
new file mode 100644
index 0000000..4592cf1
--- /dev/null
+++ b/dynamicgrid/src/org/askerov/dynamicgrid/DynamicGridUtils.java
@@ -0,0 +1,47 @@
+package org.askerov.dynamicgrid;
+
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Author: alex askerov
+ * Date: 9/7/13
+ * Time: 10:14 PM
+ */
+public class DynamicGridUtils {
+ /**
+ * Delete item in list from position indexFrom and insert it to indexTwo
+ *
+ * @param list
+ * @param indexFrom
+ * @param indexTwo
+ */
+ public static void reorder(List list, int indexFrom, int indexTwo) {
+ Object obj = list.remove(indexFrom);
+ list.add(indexTwo, obj);
+ }
+
+ /**
+ * Swap item in list at position firstIndex with item at position secondIndex
+ *
+ * @param list The list in which to swap the items.
+ * @param firstIndex The position of the first item in the list.
+ * @param secondIndex The position of the second item in the list.
+ */
+ public static void swap(List list, int firstIndex, int secondIndex) {
+ Object firstObject = list.get(firstIndex);
+ Object secondObject = list.get(secondIndex);
+ list.set(firstIndex, secondObject);
+ list.set(secondIndex, firstObject);
+ }
+
+ public static float getViewX(View view) {
+ return Math.abs((view.getRight() - view.getLeft()) / 2);
+ }
+
+ public static float getViewY(View view) {
+ return Math.abs((view.getBottom() - view.getTop()) / 2);
+ }
+}
diff --git a/example/AndroidManifest.xml b/example/AndroidManifest.xml
new file mode 100644
index 0000000..e6f7e50
--- /dev/null
+++ b/example/AndroidManifest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/build.gradle b/example/build.gradle
new file mode 100644
index 0000000..f904758
--- /dev/null
+++ b/example/build.gradle
@@ -0,0 +1,37 @@
+apply plugin: 'com.android.application'
+
+dependencies {
+ implementation project(':dynamicgrid')
+ //noinspection GradleCompatible
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.2'
+ implementation 'com.android.support:support-v4:28.0.0'
+ implementation 'com.android.support:design:28.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+// implementation project(path: ':dynamicgrid')
+// compile project(path: ':dynamicgrid')
+// compile project(path: ':dynamicgrid')
+}
+
+android {
+ compileSdkVersion 28
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = ['src']
+ resources.srcDirs = ['src']
+ aidl.srcDirs = ['src']
+ renderscript.srcDirs = ['src']
+ res.srcDirs = ['res']
+ assets.srcDirs = ['assets']
+ }
+ }
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 28
+ }
+}
\ No newline at end of file
diff --git a/example/res/drawable-hdpi/ic_launcher.png b/example/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/example/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/example/res/drawable-ldpi/ic_launcher.png b/example/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/example/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/example/res/drawable-mdpi/ic_launcher.png b/example/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/example/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/example/res/drawable-xhdpi/ic_launcher.png b/example/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/example/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/example/res/layout/activity_grid.xml b/example/res/layout/activity_grid.xml
new file mode 100644
index 0000000..7a4a631
--- /dev/null
+++ b/example/res/layout/activity_grid.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/res/layout/item_grid.xml b/example/res/layout/item_grid.xml
new file mode 100644
index 0000000..46be449
--- /dev/null
+++ b/example/res/layout/item_grid.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/res/values/integers.xml b/example/res/values/integers.xml
new file mode 100644
index 0000000..8d77500
--- /dev/null
+++ b/example/res/values/integers.xml
@@ -0,0 +1,4 @@
+
+
+ 3
+
\ No newline at end of file
diff --git a/example/res/values/strings.xml b/example/res/values/strings.xml
new file mode 100644
index 0000000..049428a
--- /dev/null
+++ b/example/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ DynamicGridExample
+
diff --git a/example/src/org/askerov/dynamicgrid/DynamicGridView.java b/example/src/org/askerov/dynamicgrid/DynamicGridView.java
new file mode 100644
index 0000000..e7e61f0
--- /dev/null
+++ b/example/src/org/askerov/dynamicgrid/DynamicGridView.java
@@ -0,0 +1,1140 @@
+package org.askerov.dynamicgrid;
+
+import android.animation.*;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Pair;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.GridView;
+import android.widget.ListAdapter;
+
+import java.util.*;
+
+/**
+ * Author: alex askerov
+ * Date: 9/6/13
+ * Time: 12:31 PM
+ */
+public class DynamicGridView extends GridView {
+ private static final int INVALID_ID = -1;
+
+ private static final int MOVE_DURATION = 300;
+ private static final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 8;
+
+ private BitmapDrawable mHoverCell;
+ private Rect mHoverCellCurrentBounds;
+ private Rect mHoverCellOriginalBounds;
+
+ private int mTotalOffsetY = 0;
+ private int mTotalOffsetX = 0;
+
+ private int mDownX = -1;
+ private int mDownY = -1;
+ private int mLastEventY = -1;
+ private int mLastEventX = -1;
+
+ //used to distinguish straight line and diagonal switching
+ private int mOverlapIfSwitchStraightLine;
+
+ private List idList = new ArrayList();
+
+ private long mMobileItemId = INVALID_ID;
+
+ private boolean mCellIsMobile = false;
+ private int mActivePointerId = INVALID_ID;
+
+ private boolean mIsMobileScrolling;
+ private int mSmoothScrollAmountAtEdge = 0;
+ private boolean mIsWaitingForScrollFinish = false;
+ private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ private boolean mIsEditMode = false;
+ private List mWobbleAnimators = new LinkedList();
+ private boolean mHoverAnimation;
+ private boolean mReorderAnimation;
+ private boolean mWobbleInEditMode = true;
+ private boolean mIsEditModeEnabled = true;
+
+ private OnScrollListener mUserScrollListener;
+ private OnDropListener mDropListener;
+ private OnDragListener mDragListener;
+ private OnEditModeChangeListener mEditModeChangeListener;
+
+ private OnItemClickListener mUserItemClickListener;
+ private OnItemClickListener mLocalItemClickListener = new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ if (!isEditMode() && isEnabled() && mUserItemClickListener != null) {
+ mUserItemClickListener.onItemClick(parent, view, position, id);
+ }
+ }
+ };
+
+ private boolean mUndoSupportEnabled;
+ private Stack mModificationStack;
+ private DynamicGridModification mCurrentModification;
+
+ private OnSelectedItemBitmapCreationListener mSelectedItemBitmapCreationListener;
+ private View mMobileView;
+
+
+ public DynamicGridView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public DynamicGridView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public DynamicGridView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ @Override
+ public void setOnScrollListener(OnScrollListener scrollListener) {
+ this.mUserScrollListener = scrollListener;
+ }
+
+ public void setOnDropListener(OnDropListener dropListener) {
+ this.mDropListener = dropListener;
+ }
+
+ public void setOnDragListener(OnDragListener dragListener) {
+ this.mDragListener = dragListener;
+ }
+
+ /**
+ * Start edit mode without starting drag;
+ */
+ public void startEditMode() {
+ startEditMode(-1);
+ }
+
+ /**
+ * Start edit mode with position. Useful for start edit mode in
+ * {@link android.widget.AdapterView.OnItemClickListener}
+ * or {@link android.widget.AdapterView.OnItemLongClickListener}
+ */
+ public void startEditMode(int position) {
+ if (!mIsEditModeEnabled)
+ return;
+ requestDisallowInterceptTouchEvent(true);
+ if (isPostHoneycomb() && mWobbleInEditMode)
+ startWobbleAnimation();
+ if (position != -1) {
+ startDragAtPosition(position);
+ }
+ mIsEditMode = true;
+ if (mEditModeChangeListener != null)
+ mEditModeChangeListener.onEditModeChanged(true);
+ }
+
+ public void stopEditMode() {
+ mIsEditMode = false;
+ requestDisallowInterceptTouchEvent(false);
+ if (isPostHoneycomb() && mWobbleInEditMode)
+ stopWobble(true);
+ if (mEditModeChangeListener != null)
+ mEditModeChangeListener.onEditModeChanged(false);
+ }
+
+ public boolean isEditModeEnabled() {
+ return mIsEditModeEnabled;
+ }
+
+ public void setEditModeEnabled(boolean enabled) {
+ this.mIsEditModeEnabled = enabled;
+ }
+
+ public void setOnEditModeChangeListener(OnEditModeChangeListener editModeChangeListener) {
+ this.mEditModeChangeListener = editModeChangeListener;
+ }
+
+ public boolean isEditMode() {
+ return mIsEditMode;
+ }
+
+ public boolean isWobbleInEditMode() {
+ return mWobbleInEditMode;
+ }
+
+ public void setWobbleInEditMode(boolean wobbleInEditMode) {
+ this.mWobbleInEditMode = wobbleInEditMode;
+ }
+
+ @Override
+ public void setOnItemClickListener(OnItemClickListener listener) {
+ this.mUserItemClickListener = listener;
+ super.setOnItemClickListener(mLocalItemClickListener);
+ }
+
+ public boolean isUndoSupportEnabled() {
+ return mUndoSupportEnabled;
+ }
+
+ public void setUndoSupportEnabled(boolean undoSupportEnabled) {
+ if (this.mUndoSupportEnabled != undoSupportEnabled) {
+ if (undoSupportEnabled) {
+ this.mModificationStack = new Stack();
+ } else {
+ this.mModificationStack = null;
+ }
+ }
+
+ this.mUndoSupportEnabled = undoSupportEnabled;
+ }
+
+ public void undoLastModification() {
+ if (mUndoSupportEnabled) {
+ if (mModificationStack != null && !mModificationStack.isEmpty()) {
+ DynamicGridModification modification = mModificationStack.pop();
+ undoModification(modification);
+ }
+ }
+ }
+
+ public void undoAllModifications() {
+ if (mUndoSupportEnabled) {
+ if (mModificationStack != null && !mModificationStack.isEmpty()) {
+ while (!mModificationStack.isEmpty()) {
+ DynamicGridModification modification = mModificationStack.pop();
+ undoModification(modification);
+ }
+ }
+ }
+ }
+
+ public boolean hasModificationHistory() {
+ if (mUndoSupportEnabled) {
+ if (mModificationStack != null && !mModificationStack.isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void clearModificationHistory() {
+ mModificationStack.clear();
+ }
+
+ public void setOnSelectedItemBitmapCreationListener(OnSelectedItemBitmapCreationListener selectedItemBitmapCreationListener) {
+ this.mSelectedItemBitmapCreationListener = selectedItemBitmapCreationListener;
+ }
+
+ private void undoModification(DynamicGridModification modification) {
+ for (Pair transition : modification.getTransitions()) {
+ reorderElements(transition.second, transition.first);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void startWobbleAnimation() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getChildAt(i);
+ if (v != null && Boolean.TRUE != v.getTag(R.id.dgv_wobble_tag)) {
+ if (i % 2 == 0)
+ animateWobble(v);
+ else
+ animateWobbleInverse(v);
+ v.setTag(R.id.dgv_wobble_tag, true);
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void stopWobble(boolean resetRotation) {
+ for (Animator wobbleAnimator : mWobbleAnimators) {
+ wobbleAnimator.cancel();
+ }
+ mWobbleAnimators.clear();
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getChildAt(i);
+ if (v != null) {
+ if (resetRotation) v.setRotation(0);
+ v.setTag(R.id.dgv_wobble_tag, false);
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void restartWobble() {
+ stopWobble(false);
+ startWobbleAnimation();
+ }
+
+ public void init(Context context) {
+ super.setOnScrollListener(mScrollListener);
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ mSmoothScrollAmountAtEdge = (int) (SMOOTH_SCROLL_AMOUNT_AT_EDGE * metrics.density + 0.5f);
+ mOverlapIfSwitchStraightLine = getResources().getDimensionPixelSize(R.dimen.dgv_overlap_if_switch_straight_line);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void animateWobble(View v) {
+ ObjectAnimator animator = createBaseWobble(v);
+ animator.setFloatValues(-2, 2);
+ animator.start();
+ mWobbleAnimators.add(animator);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void animateWobbleInverse(View v) {
+ ObjectAnimator animator = createBaseWobble(v);
+ animator.setFloatValues(2, -2);
+ animator.start();
+ mWobbleAnimators.add(animator);
+ }
+
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private ObjectAnimator createBaseWobble(final View v) {
+
+ if (!isPreLollipop())
+ v.setLayerType(LAYER_TYPE_SOFTWARE, null);
+
+ ObjectAnimator animator = new ObjectAnimator();
+ animator.setDuration(180);
+ animator.setRepeatMode(ValueAnimator.REVERSE);
+ animator.setRepeatCount(ValueAnimator.INFINITE);
+ animator.setPropertyName("rotation");
+ animator.setTarget(v);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setLayerType(LAYER_TYPE_NONE, null);
+ }
+ });
+ return animator;
+ }
+
+
+ private void reorderElements(int originalPosition, int targetPosition) {
+ if (mDragListener != null)
+ mDragListener.onDragPositionsChanged(originalPosition, targetPosition);
+ getAdapterInterface().reorderItems(originalPosition, targetPosition);
+ }
+
+ private int getColumnCount() {
+ return getAdapterInterface().getColumnCount();
+ }
+
+ private DynamicGridAdapterInterface getAdapterInterface() {
+ return ((DynamicGridAdapterInterface) getAdapter());
+ }
+
+ /**
+ * Creates the hover cell with the appropriate bitmap and of appropriate
+ * size. The hover cell's BitmapDrawable is drawn on top of the bitmap every
+ * single time an invalidate call is made.
+ */
+ private BitmapDrawable getAndAddHoverView(View v) {
+
+ int w = v.getWidth();
+ int h = v.getHeight();
+ int top = v.getTop();
+ int left = v.getLeft();
+
+ Bitmap b = getBitmapFromView(v);
+
+ BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
+
+ mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
+ mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds);
+
+ drawable.setBounds(mHoverCellCurrentBounds);
+
+ return drawable;
+ }
+
+ /**
+ * Returns a bitmap showing a screenshot of the view passed in.
+ */
+ private Bitmap getBitmapFromView(View v) {
+ Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ v.draw(canvas);
+ return bitmap;
+ }
+
+
+ private void updateNeighborViewsForId(long itemId) {
+ idList.clear();
+ int draggedPos = getPositionForID(itemId);
+ for (int pos = getFirstVisiblePosition(); pos <= getLastVisiblePosition(); pos++) {
+ if (draggedPos != pos && getAdapterInterface().canReorder(pos)) {
+ idList.add(getId(pos));
+ }
+ }
+ }
+
+ /**
+ * Retrieves the position in the grid corresponding to itemId
+ */
+ public int getPositionForID(long itemId) {
+ View v = getViewForId(itemId);
+ if (v == null) {
+ return -1;
+ } else {
+ return getPositionForView(v);
+ }
+ }
+
+ public View getViewForId(long itemId) {
+ int firstVisiblePosition = getFirstVisiblePosition();
+ ListAdapter adapter = getAdapter();
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getChildAt(i);
+ int position = firstVisiblePosition + i;
+ long id = adapter.getItemId(position);
+ if (id == itemId) {
+ return v;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ mDownX = (int) event.getX();
+ mDownY = (int) event.getY();
+ mActivePointerId = event.getPointerId(0);
+ if (mIsEditMode && isEnabled()) {
+ layoutChildren();
+ int position = pointToPosition(mDownX, mDownY);
+ startDragAtPosition(position);
+ } else if (!isEnabled()) {
+ return false;
+ }
+
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mActivePointerId == INVALID_ID) {
+ break;
+ }
+
+ int pointerIndex = event.findPointerIndex(mActivePointerId);
+
+ mLastEventY = (int) event.getY(pointerIndex);
+ mLastEventX = (int) event.getX(pointerIndex);
+ int deltaY = mLastEventY - mDownY;
+ int deltaX = mLastEventX - mDownX;
+
+ if (mCellIsMobile) {
+ mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left + deltaX + mTotalOffsetX,
+ mHoverCellOriginalBounds.top + deltaY + mTotalOffsetY);
+ mHoverCell.setBounds(mHoverCellCurrentBounds);
+ invalidate();
+ handleCellSwitch();
+ mIsMobileScrolling = false;
+ handleMobileCellScroll();
+ return false;
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ touchEventsEnded();
+
+ if (mUndoSupportEnabled) {
+ if (mCurrentModification != null && !mCurrentModification.getTransitions().isEmpty()) {
+ mModificationStack.push(mCurrentModification);
+ mCurrentModification = new DynamicGridModification();
+ }
+ }
+
+ if (mHoverCell != null) {
+ if (mDropListener != null) {
+ mDropListener.onActionDrop();
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ touchEventsCancelled();
+
+ if (mHoverCell != null) {
+ if (mDropListener != null) {
+ mDropListener.onActionDrop();
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ /* If a multitouch event took place and the original touch dictating
+ * the movement of the hover cell has ended, then the dragging event
+ * ends and the hover cell is animated to its corresponding position
+ * in the listview. */
+ pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+ MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = event.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ touchEventsEnded();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return super.onTouchEvent(event);
+ }
+
+ private void startDragAtPosition(int position) {
+ mTotalOffsetY = 0;
+ mTotalOffsetX = 0;
+ int itemNum = position - getFirstVisiblePosition();
+ View selectedView = getChildAt(itemNum);
+ if (selectedView != null) {
+ mMobileItemId = getAdapter().getItemId(position);
+ if (mSelectedItemBitmapCreationListener != null)
+ mSelectedItemBitmapCreationListener.onPreSelectedItemBitmapCreation(selectedView, position, mMobileItemId);
+ mHoverCell = getAndAddHoverView(selectedView);
+ if (mSelectedItemBitmapCreationListener != null)
+ mSelectedItemBitmapCreationListener.onPostSelectedItemBitmapCreation(selectedView, position, mMobileItemId);
+ if (isPostHoneycomb())
+ selectedView.setVisibility(View.INVISIBLE);
+ mCellIsMobile = true;
+ updateNeighborViewsForId(mMobileItemId);
+ if (mDragListener != null) {
+ mDragListener.onDragStarted(position);
+ }
+ }
+ }
+
+ private void handleMobileCellScroll() {
+ mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds);
+ }
+
+ public boolean handleMobileCellScroll(Rect r) {
+ int offset = computeVerticalScrollOffset();
+ int height = getHeight();
+ int extent = computeVerticalScrollExtent();
+ int range = computeVerticalScrollRange();
+ int hoverViewTop = r.top;
+ int hoverHeight = r.height();
+
+ if (hoverViewTop <= 0 && offset > 0) {
+ smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
+ return true;
+ }
+
+ if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) {
+ smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void setAdapter(ListAdapter adapter) {
+ super.setAdapter(adapter);
+ }
+
+ private void touchEventsEnded() {
+ final View mobileView = getViewForId(mMobileItemId);
+ if (mobileView != null && (mCellIsMobile || mIsWaitingForScrollFinish)) {
+ mCellIsMobile = false;
+ mIsWaitingForScrollFinish = false;
+ mIsMobileScrolling = false;
+ mActivePointerId = INVALID_ID;
+
+ // If the autoscroller has not completed scrolling, we need to wait for it to
+ // finish in order to determine the final location of where the hover cell
+ // should be animated to.
+ if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+ mIsWaitingForScrollFinish = true;
+ return;
+ }
+
+ mHoverCellCurrentBounds.offsetTo(mobileView.getLeft(), mobileView.getTop());
+
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
+ animateBounds(mobileView);
+ } else {
+ mHoverCell.setBounds(mHoverCellCurrentBounds);
+ invalidate();
+ reset(mobileView);
+ }
+ } else {
+ touchEventsCancelled();
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void animateBounds(final View mobileView) {
+ TypeEvaluator sBoundEvaluator = new TypeEvaluator() {
+ public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
+ return new Rect(interpolate(startValue.left, endValue.left, fraction),
+ interpolate(startValue.top, endValue.top, fraction),
+ interpolate(startValue.right, endValue.right, fraction),
+ interpolate(startValue.bottom, endValue.bottom, fraction));
+ }
+
+ public int interpolate(int start, int end, float fraction) {
+ return (int) (start + fraction * (end - start));
+ }
+ };
+
+
+ ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds",
+ sBoundEvaluator, mHoverCellCurrentBounds);
+ hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ invalidate();
+ }
+ });
+ hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mHoverAnimation = true;
+ updateEnableState();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHoverAnimation = false;
+ updateEnableState();
+ reset(mobileView);
+ }
+ });
+ hoverViewAnimator.start();
+ }
+
+ private void reset(View mobileView) {
+ idList.clear();
+ mMobileItemId = INVALID_ID;
+ mobileView.setVisibility(View.VISIBLE);
+ mHoverCell = null;
+ if (isPostHoneycomb() && mWobbleInEditMode) {
+ if (mIsEditMode) {
+ restartWobble();
+ } else{
+ stopWobble(true);
+ }
+ }
+ //ugly fix for unclear disappearing items after reorder
+ for (int i = 0; i < getLastVisiblePosition() - getFirstVisiblePosition(); i++) {
+ View child = getChildAt(i);
+ if (child != null) {
+ child.setVisibility(View.VISIBLE);
+ }
+ }
+ invalidate();
+ }
+
+ private void updateEnableState() {
+ setEnabled(!mHoverAnimation && !mReorderAnimation);
+ }
+
+ /**
+ * Seems that GridView before HONEYCOMB not support stable id in proper way.
+ * That cause bugs on view recycle if we will animate or change visibility state for items.
+ *
+ * @return
+ */
+ private boolean isPostHoneycomb() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+ }
+
+ /**
+ * The GridView from Android Lollipoop requires some different
+ * setVisibility() logic when switching cells.
+ *
+ * @return true if OS version is less than Lollipop, false if not
+ */
+ public static boolean isPreLollipop() {
+ return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
+ }
+
+ private void touchEventsCancelled() {
+ View mobileView = getViewForId(mMobileItemId);
+ if (mCellIsMobile) {
+ reset(mobileView);
+ }
+ mCellIsMobile = false;
+ mIsMobileScrolling = false;
+ mActivePointerId = INVALID_ID;
+
+ }
+
+ private void handleCellSwitch() {
+ final int deltaY = mLastEventY - mDownY;
+ final int deltaX = mLastEventX - mDownX;
+ final int deltaYTotal = mHoverCellOriginalBounds.centerY() + mTotalOffsetY + deltaY;
+ final int deltaXTotal = mHoverCellOriginalBounds.centerX() + mTotalOffsetX + deltaX;
+ mMobileView = getViewForId(mMobileItemId);
+ View targetView = null;
+ float vX = 0;
+ float vY = 0;
+ Point mobileColumnRowPair = getColumnAndRowForView(mMobileView);
+ for (Long id : idList) {
+ View view = getViewForId(id);
+ if (view != null) {
+ Point targetColumnRowPair = getColumnAndRowForView(view);
+ if ((aboveRight(targetColumnRowPair, mobileColumnRowPair)
+ && deltaYTotal < view.getBottom() && deltaXTotal > view.getLeft()
+ || aboveLeft(targetColumnRowPair, mobileColumnRowPair)
+ && deltaYTotal < view.getBottom() && deltaXTotal < view.getRight()
+ || belowRight(targetColumnRowPair, mobileColumnRowPair)
+ && deltaYTotal > view.getTop() && deltaXTotal > view.getLeft()
+ || belowLeft(targetColumnRowPair, mobileColumnRowPair)
+ && deltaYTotal > view.getTop() && deltaXTotal < view.getRight()
+ || above(targetColumnRowPair, mobileColumnRowPair)
+ && deltaYTotal < view.getBottom() - mOverlapIfSwitchStraightLine
+ || below(targetColumnRowPair, mobileColumnRowPair)
+ && deltaYTotal > view.getTop() + mOverlapIfSwitchStraightLine
+ || right(targetColumnRowPair, mobileColumnRowPair)
+ && deltaXTotal > view.getLeft() + mOverlapIfSwitchStraightLine
+ || left(targetColumnRowPair, mobileColumnRowPair)
+ && deltaXTotal < view.getRight() - mOverlapIfSwitchStraightLine)) {
+ float xDiff = Math.abs(DynamicGridUtils.getViewX(view) - DynamicGridUtils.getViewX(mMobileView));
+ float yDiff = Math.abs(DynamicGridUtils.getViewY(view) - DynamicGridUtils.getViewY(mMobileView));
+ if (xDiff >= vX && yDiff >= vY) {
+ vX = xDiff;
+ vY = yDiff;
+ targetView = view;
+ }
+ }
+ }
+ }
+ if (targetView != null) {
+ final int originalPosition = getPositionForView(mMobileView);
+ int targetPosition = getPositionForView(targetView);
+
+ final DynamicGridAdapterInterface adapter = getAdapterInterface();
+ if (targetPosition == INVALID_POSITION || !adapter.canReorder(originalPosition) || !adapter.canReorder(targetPosition)) {
+ updateNeighborViewsForId(mMobileItemId);
+ return;
+ }
+ reorderElements(originalPosition, targetPosition);
+
+ if (mUndoSupportEnabled) {
+ mCurrentModification.addTransition(originalPosition, targetPosition);
+ }
+
+ mDownY = mLastEventY;
+ mDownX = mLastEventX;
+
+ SwitchCellAnimator switchCellAnimator;
+
+ if (isPostHoneycomb() && isPreLollipop()) //Between Android 3.0 and Android L
+ switchCellAnimator = new KitKatSwitchCellAnimator(deltaX, deltaY);
+ else if (isPreLollipop()) //Before Android 3.0
+ switchCellAnimator = new PreHoneycombCellAnimator(deltaX, deltaY);
+ else //Android L
+ switchCellAnimator = new LSwitchCellAnimator(deltaX, deltaY);
+
+ updateNeighborViewsForId(mMobileItemId);
+
+ switchCellAnimator.animateSwitchCell(originalPosition, targetPosition);
+ }
+ }
+
+ private interface SwitchCellAnimator {
+ void animateSwitchCell(final int originalPosition, final int targetPosition);
+ }
+
+ private class PreHoneycombCellAnimator implements SwitchCellAnimator {
+ private int mDeltaY;
+ private int mDeltaX;
+
+ public PreHoneycombCellAnimator(int deltaX, int deltaY) {
+ mDeltaX = deltaX;
+ mDeltaY = deltaY;
+ }
+
+ @Override
+ public void animateSwitchCell(int originalPosition, int targetPosition) {
+ mTotalOffsetY += mDeltaY;
+ mTotalOffsetX += mDeltaX;
+ }
+ }
+
+ /**
+ * A {@link org.askerov.dynamicgrid.DynamicGridView.SwitchCellAnimator} for versions KitKat and below.
+ */
+ private class KitKatSwitchCellAnimator implements SwitchCellAnimator {
+
+ private int mDeltaY;
+ private int mDeltaX;
+
+ public KitKatSwitchCellAnimator(int deltaX, int deltaY) {
+ mDeltaX = deltaX;
+ mDeltaY = deltaY;
+ }
+
+ @Override
+ public void animateSwitchCell(final int originalPosition, final int targetPosition) {
+ assert mMobileView != null;
+ getViewTreeObserver().addOnPreDrawListener(new AnimateSwitchViewOnPreDrawListener(mMobileView, originalPosition, targetPosition));
+ mMobileView = getViewForId(mMobileItemId);
+ }
+
+ private class AnimateSwitchViewOnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
+
+ private final View mPreviousMobileView;
+ private final int mOriginalPosition;
+ private final int mTargetPosition;
+
+ AnimateSwitchViewOnPreDrawListener(final View previousMobileView, final int originalPosition, final int targetPosition) {
+ mPreviousMobileView = previousMobileView;
+ mOriginalPosition = originalPosition;
+ mTargetPosition = targetPosition;
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ getViewTreeObserver().removeOnPreDrawListener(this);
+
+ mTotalOffsetY += mDeltaY;
+ mTotalOffsetX += mDeltaX;
+
+ animateReorder(mOriginalPosition, mTargetPosition);
+
+ mPreviousMobileView.setVisibility(View.VISIBLE);
+
+ if (mMobileView != null) {
+ mMobileView.setVisibility(View.INVISIBLE);
+ }
+ return true;
+ }
+ }
+ }
+
+ /**
+ * A {@link org.askerov.dynamicgrid.DynamicGridView.SwitchCellAnimator} for versions L and above.
+ */
+ private class LSwitchCellAnimator implements SwitchCellAnimator {
+
+ private int mDeltaY;
+ private int mDeltaX;
+
+ public LSwitchCellAnimator(int deltaX, int deltaY) {
+ mDeltaX = deltaX;
+ mDeltaY = deltaY;
+ }
+
+ @Override
+ public void animateSwitchCell(final int originalPosition, final int targetPosition) {
+ getViewTreeObserver().addOnPreDrawListener(new AnimateSwitchViewOnPreDrawListener(originalPosition, targetPosition));
+ }
+
+ private class AnimateSwitchViewOnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
+ private final int mOriginalPosition;
+ private final int mTargetPosition;
+
+ AnimateSwitchViewOnPreDrawListener(final int originalPosition, final int targetPosition) {
+ mOriginalPosition = originalPosition;
+ mTargetPosition = targetPosition;
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ getViewTreeObserver().removeOnPreDrawListener(this);
+
+ mTotalOffsetY += mDeltaY;
+ mTotalOffsetX += mDeltaX;
+
+ animateReorder(mOriginalPosition, mTargetPosition);
+
+ assert mMobileView != null;
+ mMobileView.setVisibility(View.VISIBLE);
+ mMobileView = getViewForId(mMobileItemId);
+ assert mMobileView != null;
+ mMobileView.setVisibility(View.INVISIBLE);
+ return true;
+ }
+ }
+ }
+
+ private boolean belowLeft(Point targetColumnRowPair, Point mobileColumnRowPair) {
+ return targetColumnRowPair.y > mobileColumnRowPair.y && targetColumnRowPair.x < mobileColumnRowPair.x;
+ }
+
+ private boolean belowRight(Point targetColumnRowPair, Point mobileColumnRowPair) {
+ return targetColumnRowPair.y > mobileColumnRowPair.y && targetColumnRowPair.x > mobileColumnRowPair.x;
+ }
+
+ private boolean aboveLeft(Point targetColumnRowPair, Point mobileColumnRowPair) {
+ return targetColumnRowPair.y < mobileColumnRowPair.y && targetColumnRowPair.x < mobileColumnRowPair.x;
+ }
+
+ private boolean aboveRight(Point targetColumnRowPair, Point mobileColumnRowPair) {
+ return targetColumnRowPair.y < mobileColumnRowPair.y && targetColumnRowPair.x > mobileColumnRowPair.x;
+ }
+
+ private boolean above(Point targetColumnRowPair, Point mobileColumnRowPair) {
+ return targetColumnRowPair.y < mobileColumnRowPair.y && targetColumnRowPair.x == mobileColumnRowPair.x;
+ }
+
+ private boolean below(Point targetColumnRowPair, Point mobileColumnRowPair) {
+ return targetColumnRowPair.y > mobileColumnRowPair.y && targetColumnRowPair.x == mobileColumnRowPair.x;
+ }
+
+ private boolean right(Point targetColumnRowPair, Point mobileColumnRowPair) {
+ return targetColumnRowPair.y == mobileColumnRowPair.y && targetColumnRowPair.x > mobileColumnRowPair.x;
+ }
+
+ private boolean left(Point targetColumnRowPair, Point mobileColumnRowPair) {
+ return targetColumnRowPair.y == mobileColumnRowPair.y && targetColumnRowPair.x < mobileColumnRowPair.x;
+ }
+
+ private Point getColumnAndRowForView(View view) {
+ int pos = getPositionForView(view);
+ int columns = getColumnCount();
+ int column = pos % columns;
+ int row = pos / columns;
+ return new Point(column, row);
+ }
+
+ private long getId(int position) {
+ return getAdapter().getItemId(position);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void animateReorder(final int oldPosition, final int newPosition) {
+ boolean isForward = newPosition > oldPosition;
+ List resultList = new LinkedList();
+ if (isForward) {
+ for (int pos = Math.min(oldPosition, newPosition); pos < Math.max(oldPosition, newPosition); pos++) {
+ View view = getViewForId(getId(pos));
+ if ((pos + 1) % getColumnCount() == 0) {
+ resultList.add(createTranslationAnimations(view, -view.getWidth() * (getColumnCount() - 1), 0,
+ view.getHeight(), 0));
+ } else {
+ resultList.add(createTranslationAnimations(view, view.getWidth(), 0, 0, 0));
+ }
+ }
+ } else {
+ for (int pos = Math.max(oldPosition, newPosition); pos > Math.min(oldPosition, newPosition); pos--) {
+ View view = getViewForId(getId(pos));
+ if ((pos + getColumnCount()) % getColumnCount() == 0) {
+ resultList.add(createTranslationAnimations(view, view.getWidth() * (getColumnCount() - 1), 0,
+ -view.getHeight(), 0));
+ } else {
+ resultList.add(createTranslationAnimations(view, -view.getWidth(), 0, 0, 0));
+ }
+ }
+ }
+
+ AnimatorSet resultSet = new AnimatorSet();
+ resultSet.playTogether(resultList);
+ resultSet.setDuration(MOVE_DURATION);
+ resultSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ resultSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mReorderAnimation = true;
+ updateEnableState();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mReorderAnimation = false;
+ updateEnableState();
+ }
+ });
+ resultSet.start();
+ }
+
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private AnimatorSet createTranslationAnimations(View view, float startX, float endX, float startY, float endY) {
+ ObjectAnimator animX = ObjectAnimator.ofFloat(view, "translationX", startX, endX);
+ ObjectAnimator animY = ObjectAnimator.ofFloat(view, "translationY", startY, endY);
+ AnimatorSet animSetXY = new AnimatorSet();
+ animSetXY.playTogether(animX, animY);
+ return animSetXY;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (mHoverCell != null) {
+ mHoverCell.draw(canvas);
+ }
+ }
+
+
+ public interface OnDropListener {
+ void onActionDrop();
+ }
+
+ public interface OnDragListener {
+
+ public void onDragStarted(int position);
+
+ public void onDragPositionsChanged(int oldPosition, int newPosition);
+ }
+
+ public interface OnEditModeChangeListener {
+ public void onEditModeChanged(boolean inEditMode);
+ }
+
+ public interface OnSelectedItemBitmapCreationListener {
+ public void onPreSelectedItemBitmapCreation(View selectedView, int position, long itemId);
+
+ public void onPostSelectedItemBitmapCreation(View selectedView, int position, long itemId);
+ }
+
+
+ /**
+ * This scroll listener is added to the gridview in order to handle cell swapping
+ * when the cell is either at the top or bottom edge of the gridview. If the hover
+ * cell is at either edge of the gridview, the gridview will begin scrolling. As
+ * scrolling takes place, the gridview continuously checks if new cells became visible
+ * and determines whether they are potential candidates for a cell swap.
+ */
+ private OnScrollListener mScrollListener = new OnScrollListener() {
+
+ private int mPreviousFirstVisibleItem = -1;
+ private int mPreviousVisibleItemCount = -1;
+ private int mCurrentFirstVisibleItem;
+ private int mCurrentVisibleItemCount;
+ private int mCurrentScrollState;
+
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ mCurrentFirstVisibleItem = firstVisibleItem;
+ mCurrentVisibleItemCount = visibleItemCount;
+
+ mPreviousFirstVisibleItem = (mPreviousFirstVisibleItem == -1) ? mCurrentFirstVisibleItem
+ : mPreviousFirstVisibleItem;
+ mPreviousVisibleItemCount = (mPreviousVisibleItemCount == -1) ? mCurrentVisibleItemCount
+ : mPreviousVisibleItemCount;
+
+ checkAndHandleFirstVisibleCellChange();
+ checkAndHandleLastVisibleCellChange();
+
+ mPreviousFirstVisibleItem = mCurrentFirstVisibleItem;
+ mPreviousVisibleItemCount = mCurrentVisibleItemCount;
+ if (isPostHoneycomb() && mWobbleInEditMode) {
+ updateWobbleState(visibleItemCount);
+ }
+ if (mUserScrollListener != null) {
+ mUserScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void updateWobbleState(int visibleItemCount) {
+ for (int i = 0; i < visibleItemCount; i++) {
+ View child = getChildAt(i);
+
+ if (child != null) {
+ if (mMobileItemId != INVALID_ID && Boolean.TRUE != child.getTag(R.id.dgv_wobble_tag)) {
+ if (i % 2 == 0)
+ animateWobble(child);
+ else
+ animateWobbleInverse(child);
+ child.setTag(R.id.dgv_wobble_tag, true);
+ } else if (mMobileItemId == INVALID_ID && child.getRotation() != 0) {
+ child.setRotation(0);
+ child.setTag(R.id.dgv_wobble_tag, false);
+ }
+ }
+
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ mCurrentScrollState = scrollState;
+ mScrollState = scrollState;
+ isScrollCompleted();
+ if (mUserScrollListener != null) {
+ mUserScrollListener.onScrollStateChanged(view, scrollState);
+ }
+ }
+
+ /**
+ * This method is in charge of invoking 1 of 2 actions. Firstly, if the gridview
+ * is in a state of scrolling invoked by the hover cell being outside the bounds
+ * of the gridview, then this scrolling event is continued. Secondly, if the hover
+ * cell has already been released, this invokes the animation for the hover cell
+ * to return to its correct position after the gridview has entered an idle scroll
+ * state.
+ */
+ private void isScrollCompleted() {
+ if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) {
+ if (mCellIsMobile && mIsMobileScrolling) {
+ handleMobileCellScroll();
+ } else if (mIsWaitingForScrollFinish) {
+ touchEventsEnded();
+ }
+ }
+ }
+
+ /**
+ * Determines if the gridview scrolled up enough to reveal a new cell at the
+ * top of the list. If so, then the appropriate parameters are updated.
+ */
+ public void checkAndHandleFirstVisibleCellChange() {
+ if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) {
+ if (mCellIsMobile && mMobileItemId != INVALID_ID) {
+ updateNeighborViewsForId(mMobileItemId);
+ handleCellSwitch();
+ }
+ }
+ }
+
+ /**
+ * Determines if the gridview scrolled down enough to reveal a new cell at the
+ * bottom of the list. If so, then the appropriate parameters are updated.
+ */
+ public void checkAndHandleLastVisibleCellChange() {
+ int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount;
+ int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount;
+ if (currentLastVisibleItem != previousLastVisibleItem) {
+ if (mCellIsMobile && mMobileItemId != INVALID_ID) {
+ updateNeighborViewsForId(mMobileItemId);
+ handleCellSwitch();
+ }
+ }
+ }
+ };
+
+ private static class DynamicGridModification {
+
+ private List> transitions;
+
+ DynamicGridModification() {
+ super();
+ this.transitions = new Stack>();
+ }
+
+ public boolean hasTransitions() {
+ return !transitions.isEmpty();
+ }
+
+ public void addTransition(int oldPosition, int newPosition) {
+ transitions.add(new Pair(oldPosition, newPosition));
+ }
+
+ public List> getTransitions() {
+ Collections.reverse(transitions);
+ return transitions;
+ }
+ }
+}
+
diff --git a/example/src/org/askerov/dynamicgrid/example/CheeseDynamicAdapter.java b/example/src/org/askerov/dynamicgrid/example/CheeseDynamicAdapter.java
new file mode 100644
index 0000000..ff2cf40
--- /dev/null
+++ b/example/src/org/askerov/dynamicgrid/example/CheeseDynamicAdapter.java
@@ -0,0 +1,57 @@
+package org.askerov.dynamicgrid.example;
+
+/**
+ * Author: alex askerov
+ * Date: 9/9/13
+ * Time: 10:52 PM
+ */
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import org.askerov.dynamicgrid.BaseDynamicGridAdapter;
+
+import java.util.List;
+
+/**
+ * Author: alex askerov
+ * Date: 9/7/13
+ * Time: 10:56 PM
+ */
+public class CheeseDynamicAdapter extends BaseDynamicGridAdapter {
+ public CheeseDynamicAdapter(Context context, List> items, int columnCount) {
+ super(context, items, columnCount);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ CheeseViewHolder holder;
+ if (convertView == null) {
+ convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_grid, null);
+ holder = new CheeseViewHolder(convertView);
+ convertView.setTag(holder);
+ } else {
+ holder = (CheeseViewHolder) convertView.getTag();
+ }
+ holder.build(getItem(position).toString());
+ return convertView;
+ }
+
+ private class CheeseViewHolder {
+ private TextView titleText;
+ private ImageView image;
+
+ private CheeseViewHolder(View view) {
+ titleText = (TextView) view.findViewById(R.id.item_title);
+ image = (ImageView) view.findViewById(R.id.item_img);
+ }
+
+ void build(String title) {
+ titleText.setText(title);
+ image.setImageResource(R.drawable.ic_launcher);
+ }
+ }
+}
\ No newline at end of file
diff --git a/example/src/org/askerov/dynamicgrid/example/Cheeses.java b/example/src/org/askerov/dynamicgrid/example/Cheeses.java
new file mode 100644
index 0000000..bf384f8
--- /dev/null
+++ b/example/src/org/askerov/dynamicgrid/example/Cheeses.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 org.askerov.dynamicgrid.example;
+
+public class Cheeses {
+
+ public static final String[] sCheeseStrings = {
+ "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
+ "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
+ "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
+ "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
+ "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
+ "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
+ "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
+ "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
+ "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
+ "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
+ "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
+ "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
+ "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
+ "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
+ "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
+ "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
+ "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
+ "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
+ "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
+ "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
+ "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
+ "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
+ "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
+ "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
+ "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
+ "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
+ "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
+ "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
+ "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
+ "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
+ "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
+ "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
+ "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
+ "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
+ "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
+ "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
+ "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
+ "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
+ "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
+ "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
+ "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
+ "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
+ "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
+ "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
+ "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
+ "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
+ "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
+ "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
+ "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
+ "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
+ "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
+ "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
+ "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
+ "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
+ "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
+ "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
+ "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
+ "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
+ "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
+ "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
+ "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
+ "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
+ "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
+ "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
+ "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
+ "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
+ "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
+ "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
+ "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
+ "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
+ "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
+ "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
+ "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
+ "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
+ "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
+ "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
+ "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
+ "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
+ "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
+ "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
+ "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
+ "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
+ "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
+ "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
+ "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
+ "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
+ "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
+ "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
+ "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
+ "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
+ "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
+ "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
+ "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
+ "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
+ "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
+ "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
+ "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
+ "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
+ "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
+ "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
+ "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
+ "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
+ "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
+ "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
+ "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
+ "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
+ "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
+ "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
+ "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
+ "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
+ "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
+ "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
+ "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
+ "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
+ "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
+ "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
+ "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
+ "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
+ "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
+ "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
+ "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
+ "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
+ "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
+ "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
+ "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
+ "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
+ "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
+ "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
+ "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
+ "Zamorano"
+ };
+
+}
diff --git a/example/src/org/askerov/dynamicgrid/example/GridActivity.java b/example/src/org/askerov/dynamicgrid/example/GridActivity.java
new file mode 100644
index 0000000..a5a95fd
--- /dev/null
+++ b/example/src/org/askerov/dynamicgrid/example/GridActivity.java
@@ -0,0 +1,73 @@
+package org.askerov.dynamicgrid.example;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Toast;
+import org.askerov.dynamicgrid.DynamicGridView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class GridActivity extends Activity {
+
+ private static final String TAG = GridActivity.class.getName();
+
+ private DynamicGridView gridView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_grid);
+ gridView = (DynamicGridView) findViewById(R.id.dynamic_grid);
+ gridView.setAdapter(new CheeseDynamicAdapter(this,
+ new ArrayList(Arrays.asList(Cheeses.sCheeseStrings)),
+ getResources().getInteger(R.integer.column_count)));
+// add callback to stop edit mode if needed
+// gridView.setOnDropListener(new DynamicGridView.OnDropListener()
+// {
+// @Override
+// public void onActionDrop()
+// {
+// gridView.stopEditMode();
+// }
+// });
+ gridView.setOnDragListener(new DynamicGridView.OnDragListener() {
+ @Override
+ public void onDragStarted(int position) {
+ Log.d(TAG, "drag started at position " + position);
+ }
+
+ @Override
+ public void onDragPositionsChanged(int oldPosition, int newPosition) {
+ Log.d(TAG, String.format("drag item position changed from %d to %d", oldPosition, newPosition));
+ }
+ });
+ gridView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
+ gridView.startEditMode(position);
+ return true;
+ }
+ });
+
+ gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ Toast.makeText(GridActivity.this, parent.getAdapter().getItem(position).toString(),
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (gridView.isEditMode()) {
+ gridView.stopEditMode();
+ } else {
+ super.onBackPressed();
+ }
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..c89d0b6
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include 'dynamicgrid', 'example'
\ No newline at end of file