├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── dynamicgrid
├── AndroidManifest.xml
├── build.gradle
├── res
│ └── values
│ │ ├── dimens.xml
│ │ └── id.xml
└── src
│ └── org
│ └── askerov
│ └── dynamicgrid
│ ├── AbstractDynamicGridAdapter.java
│ ├── BaseDynamicGridAdapter.java
│ ├── DynamicGridAdapterInterface.java
│ ├── DynamicGridUtils.java
│ └── DynamicGridView.java
├── example
├── AndroidManifest.xml
├── build.gradle
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-ldpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ ├── activity_grid.xml
│ │ └── item_grid.xml
│ └── values
│ │ ├── integers.xml
│ │ └── strings.xml
└── src
│ └── org
│ └── askerov
│ └── dynamicgrid
│ └── example
│ ├── CheeseDynamicAdapter.java
│ ├── Cheeses.java
│ └── GridActivity.java
├── gradle
└── wrapper
│ └── gradle-wrapper.jar
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | *.properties
16 |
17 | # Eclipse project files
18 | .classpath
19 | .project
20 |
21 | .idea/
22 | build/
23 | *.iml
24 |
25 | dynamicgrid/proguard-project.txt
26 |
27 | dynamicgrid/build.xml
28 |
29 | dynamicgrid/ant.properties
30 |
31 | example/build.xml
32 |
33 | example/proguard-project.txt
34 |
35 | example/modules/
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "[]" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright [yyyy] [name of copyright owner]
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | DynamicGrid
2 | ===========
3 |
4 | Drag and drop GridView for Android.
5 |
6 | Depricated
7 | ===========
8 | It's much better to use solutions based on recycler view. For example https://github.com/h6ah4i/android-advancedrecyclerview
9 |
10 | Demo
11 | ----
12 |
13 |
16 |
17 | Requirements
18 | ----------
19 | Rearranging items require api 8 (Froyo).
20 | All grid item animations require api 11 (Honeycomb).
21 |
22 | Usage
23 | ----------
24 | All the same as for normal GridView. Adapter must extends
25 | [AbstractDynamicGridAdapter](https://github.com/askerov/DynamicGrid/blob/master/dynamicgrid/src/org/askerov/dynamicgid/AbstractDynamicGridAdapter.java "AbstractDynamicGridAdapter")
26 | or [BaseDynamicGridAdapter](https://github.com/askerov/DynamicGrid/blob/master/dynamicgrid/src/org/askerov/dynamicgid/BaseDynamicGridAdapter.java "BaseDynamicGridAdapter")
27 |
28 | ```java
29 | gridView = (DynamicGridView) findViewById(R.id.dynamic_grid);
30 | // pass to adapter context, list of items and number of columns count
31 | gridView.setAdapter(new MyDynamicGridAdapter(this, itemsList, 3));
32 | ```
33 |
34 | To start Drag'n'drop mode:
35 |
36 | ```java
37 | gridView.startEditMode();
38 | ```
39 |
40 | Or from onItemClik() and onItemLongClick()
41 |
42 | ```java
43 | gridView.startEditMode(position);
44 | ```
45 |
46 | To stop:
47 |
48 | ```java
49 | gridView.stopEditMode();
50 | ```
51 |
52 | Adding drop listener:
53 |
54 | ```java
55 | gridView.setOnDropListener(new DynamicGridView.OnDropListener(){
56 | @Override
57 | public void onActionDrop(){
58 | // stop edit mode immediately after drop item
59 | gridView.stopEditMode();
60 | }
61 | });
62 | ```
63 |
64 | You can find more detailed usage example [here](https://github.com/askerov/DynamicGrid/tree/master/example).
65 |
66 | Credits
67 | --------
68 | DynamicGridView based on [Daniel Olshansky](https://plus.google.com/108153578400873445224/) ListView cell dragging and rearranging [example](https://www.youtube.com/watch?v=_BZIvjMgH-Q).
69 |
70 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.0.0-rc4'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | mavenCentral()
15 | }
16 | }
--------------------------------------------------------------------------------
/dynamicgrid/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/dynamicgrid/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.1"
6 |
7 | defaultConfig {
8 | minSdkVersion 8
9 | targetSdkVersion 21
10 | }
11 |
12 | sourceSets {
13 | main {
14 | manifest.srcFile 'AndroidManifest.xml'
15 | java.srcDirs = ['src']
16 | resources.srcDirs = ['src']
17 | aidl.srcDirs = ['src']
18 | renderscript.srcDirs = ['src']
19 | res.srcDirs = ['res']
20 | assets.srcDirs = ['assets']
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/dynamicgrid/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 |
--------------------------------------------------------------------------------
/dynamicgrid/res/values/id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/dynamicgrid/src/org/askerov/dynamicgrid/AbstractDynamicGridAdapter.java:
--------------------------------------------------------------------------------
1 | package org.askerov.dynamicgrid;
2 |
3 | import android.widget.BaseAdapter;
4 |
5 | import java.util.HashMap;
6 | import java.util.List;
7 |
8 | /**
9 | * Author: alex askerov
10 | * Date: 9/6/13
11 | * Time: 7:43 PM
12 | */
13 |
14 |
15 | /**
16 | * Abstract adapter for {@link org.askerov.dynamicgrid.DynamicGridView} with sable items id;
17 | */
18 |
19 | public abstract class AbstractDynamicGridAdapter extends BaseAdapter implements DynamicGridAdapterInterface {
20 | public static final int INVALID_ID = -1;
21 |
22 | private int nextStableId = 0;
23 |
24 | private HashMap mIdMap = new HashMap();
25 |
26 | /**
27 | * Adapter must have stable id
28 | *
29 | * @return
30 | */
31 | @Override
32 | public final boolean hasStableIds() {
33 | return true;
34 | }
35 |
36 | /**
37 | * creates stable id for object
38 | *
39 | * @param item
40 | */
41 | protected void addStableId(Object item) {
42 | mIdMap.put(item, nextStableId++);
43 | }
44 |
45 | /**
46 | * create stable ids for list
47 | *
48 | * @param items
49 | */
50 | protected void addAllStableId(List> items) {
51 | for (Object item : items) {
52 | addStableId(item);
53 | }
54 | }
55 |
56 | /**
57 | * get id for position
58 | *
59 | * @param position
60 | * @return
61 | */
62 | @Override
63 | public final long getItemId(int position) {
64 | if (position < 0 || position >= mIdMap.size()) {
65 | return INVALID_ID;
66 | }
67 | Object item = getItem(position);
68 | return mIdMap.get(item);
69 | }
70 |
71 | /**
72 | * clear stable id map
73 | * should called when clear adapter data;
74 | */
75 | protected void clearStableIdMap() {
76 | mIdMap.clear();
77 | }
78 |
79 | /**
80 | * remove stable id for item
. Should called on remove data item from adapter
81 | *
82 | * @param item
83 | */
84 | protected void removeStableID(Object item) {
85 | mIdMap.remove(item);
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/dynamicgrid/src/org/askerov/dynamicgrid/BaseDynamicGridAdapter.java:
--------------------------------------------------------------------------------
1 | package org.askerov.dynamicgrid;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | /**
9 | * Author: alex askerov
10 | * Date: 9/7/13
11 | * Time: 10:49 PM
12 | */
13 | public abstract class BaseDynamicGridAdapter extends AbstractDynamicGridAdapter {
14 | private Context mContext;
15 |
16 | private ArrayList mItems = new ArrayList();
17 | private int mColumnCount;
18 |
19 | protected BaseDynamicGridAdapter(Context context, int columnCount) {
20 | this.mContext = context;
21 | this.mColumnCount = columnCount;
22 | }
23 |
24 | public BaseDynamicGridAdapter(Context context, List> items, int columnCount) {
25 | mContext = context;
26 | mColumnCount = columnCount;
27 | init(items);
28 | }
29 |
30 | private void init(List> items) {
31 | addAllStableId(items);
32 | this.mItems.addAll(items);
33 | }
34 |
35 |
36 | public void set(List> items) {
37 | clear();
38 | init(items);
39 | notifyDataSetChanged();
40 | }
41 |
42 | public void clear() {
43 | clearStableIdMap();
44 | mItems.clear();
45 | notifyDataSetChanged();
46 | }
47 |
48 | public void add(Object item) {
49 | addStableId(item);
50 | mItems.add(item);
51 | notifyDataSetChanged();
52 | }
53 |
54 | public void add(int position, Object item) {
55 | addStableId(item);
56 | mItems.add(position, item);
57 | notifyDataSetChanged();
58 | }
59 |
60 | public void add(List> items) {
61 | addAllStableId(items);
62 | this.mItems.addAll(items);
63 | notifyDataSetChanged();
64 | }
65 |
66 |
67 | public void remove(Object item) {
68 | mItems.remove(item);
69 | removeStableID(item);
70 | notifyDataSetChanged();
71 | }
72 |
73 |
74 | @Override
75 | public int getCount() {
76 | return mItems.size();
77 | }
78 |
79 | @Override
80 | public Object getItem(int position) {
81 | return mItems.get(position);
82 | }
83 |
84 | @Override
85 | public int getColumnCount() {
86 | return mColumnCount;
87 | }
88 |
89 | public void setColumnCount(int columnCount) {
90 | this.mColumnCount = columnCount;
91 | notifyDataSetChanged();
92 | }
93 |
94 | @Override
95 | public void reorderItems(int originalPosition, int newPosition) {
96 | if (newPosition < getCount()) {
97 | DynamicGridUtils.reorder(mItems, originalPosition, newPosition);
98 | notifyDataSetChanged();
99 | }
100 | }
101 |
102 | @Override
103 | public boolean canReorder(int position) {
104 | return true;
105 | }
106 |
107 | public List getItems() {
108 | return mItems;
109 | }
110 |
111 | protected Context getContext() {
112 | return mContext;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/dynamicgrid/src/org/askerov/dynamicgrid/DynamicGridAdapterInterface.java:
--------------------------------------------------------------------------------
1 | package org.askerov.dynamicgrid;
2 |
3 | /**
4 | * Author: alex askerov
5 | * Date: 18/07/14
6 | * Time: 23:44
7 | */
8 |
9 | /**
10 | * Any adapter used with DynamicGridView must implement DynamicGridAdapterInterface.
11 | * Adapter implementation also must has stable items id.
12 | * See {@link org.askerov.dynamicgrid.AbstractDynamicGridAdapter} for stable id implementation example.
13 | */
14 |
15 | public interface DynamicGridAdapterInterface {
16 |
17 | /**
18 | * Determines how to reorder items dragged from originalPosition
to newPosition
19 | */
20 | void reorderItems(int originalPosition, int newPosition);
21 |
22 | /**
23 | * @return return columns number for GridView. Need for compatibility
24 | * (@link android.widget.GridView#getNumColumns() requires api 11)
25 | */
26 | int getColumnCount();
27 |
28 | /**
29 | * Determines whether the item in the specified position
can be reordered.
30 | */
31 | boolean canReorder(int position);
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/dynamicgrid/src/org/askerov/dynamicgrid/DynamicGridUtils.java:
--------------------------------------------------------------------------------
1 | package org.askerov.dynamicgrid;
2 |
3 | import android.view.View;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | /**
9 | * Author: alex askerov
10 | * Date: 9/7/13
11 | * Time: 10:14 PM
12 | */
13 | public class DynamicGridUtils {
14 | /**
15 | * Delete item in list
from position indexFrom
and insert it to indexTwo
16 | *
17 | * @param list
18 | * @param indexFrom
19 | * @param indexTwo
20 | */
21 | public static void reorder(List list, int indexFrom, int indexTwo) {
22 | Object obj = list.remove(indexFrom);
23 | list.add(indexTwo, obj);
24 | }
25 |
26 | /**
27 | * Swap item in list
at position firstIndex
with item at position secondIndex
28 | *
29 | * @param list The list in which to swap the items.
30 | * @param firstIndex The position of the first item in the list.
31 | * @param secondIndex The position of the second item in the list.
32 | */
33 | public static void swap(List list, int firstIndex, int secondIndex) {
34 | Object firstObject = list.get(firstIndex);
35 | Object secondObject = list.get(secondIndex);
36 | list.set(firstIndex, secondObject);
37 | list.set(secondIndex, firstObject);
38 | }
39 |
40 | public static float getViewX(View view) {
41 | return Math.abs((view.getRight() - view.getLeft()) / 2);
42 | }
43 |
44 | public static float getViewY(View view) {
45 | return Math.abs((view.getBottom() - view.getTop()) / 2);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/dynamicgrid/src/org/askerov/dynamicgrid/DynamicGridView.java:
--------------------------------------------------------------------------------
1 | package org.askerov.dynamicgrid;
2 |
3 | import android.animation.*;
4 | import android.annotation.TargetApi;
5 | import android.content.Context;
6 | import android.graphics.Bitmap;
7 | import android.graphics.Canvas;
8 | import android.graphics.Point;
9 | import android.graphics.Rect;
10 | import android.graphics.drawable.BitmapDrawable;
11 | import android.os.Build;
12 | import android.util.AttributeSet;
13 | import android.util.DisplayMetrics;
14 | import android.util.Pair;
15 | import android.view.MotionEvent;
16 | import android.view.View;
17 | import android.view.ViewTreeObserver;
18 | import android.view.animation.AccelerateDecelerateInterpolator;
19 | import android.widget.AbsListView;
20 | import android.widget.AdapterView;
21 | import android.widget.GridView;
22 | import android.widget.ListAdapter;
23 |
24 | import java.util.*;
25 |
26 | /**
27 | * Author: alex askerov
28 | * Date: 9/6/13
29 | * Time: 12:31 PM
30 | */
31 | public class DynamicGridView extends GridView {
32 | private static final int INVALID_ID = -1;
33 |
34 | private static final int MOVE_DURATION = 300;
35 | private static final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 8;
36 |
37 | private BitmapDrawable mHoverCell;
38 | private Rect mHoverCellCurrentBounds;
39 | private Rect mHoverCellOriginalBounds;
40 |
41 | private int mTotalOffsetY = 0;
42 | private int mTotalOffsetX = 0;
43 |
44 | private int mDownX = -1;
45 | private int mDownY = -1;
46 | private int mLastEventY = -1;
47 | private int mLastEventX = -1;
48 |
49 | //used to distinguish straight line and diagonal switching
50 | private int mOverlapIfSwitchStraightLine;
51 |
52 | private List idList = new ArrayList();
53 |
54 | private long mMobileItemId = INVALID_ID;
55 |
56 | private boolean mCellIsMobile = false;
57 | private int mActivePointerId = INVALID_ID;
58 |
59 | private boolean mIsMobileScrolling;
60 | private int mSmoothScrollAmountAtEdge = 0;
61 | private boolean mIsWaitingForScrollFinish = false;
62 | private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
63 |
64 | private boolean mIsEditMode = false;
65 | private List mWobbleAnimators = new LinkedList();
66 | private boolean mHoverAnimation;
67 | private boolean mReorderAnimation;
68 | private boolean mWobbleInEditMode = true;
69 | private boolean mIsEditModeEnabled = true;
70 |
71 | private OnScrollListener mUserScrollListener;
72 | private OnDropListener mDropListener;
73 | private OnDragListener mDragListener;
74 | private OnEditModeChangeListener mEditModeChangeListener;
75 |
76 | private OnItemClickListener mUserItemClickListener;
77 | private OnItemClickListener mLocalItemClickListener = new OnItemClickListener() {
78 | @Override
79 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
80 | if (!isEditMode() && isEnabled() && mUserItemClickListener != null) {
81 | mUserItemClickListener.onItemClick(parent, view, position, id);
82 | }
83 | }
84 | };
85 |
86 | private boolean mUndoSupportEnabled;
87 | private Stack mModificationStack;
88 | private DynamicGridModification mCurrentModification;
89 |
90 | private OnSelectedItemBitmapCreationListener mSelectedItemBitmapCreationListener;
91 | private View mMobileView;
92 |
93 |
94 | public DynamicGridView(Context context) {
95 | super(context);
96 | init(context);
97 | }
98 |
99 | public DynamicGridView(Context context, AttributeSet attrs) {
100 | super(context, attrs);
101 | init(context);
102 | }
103 |
104 | public DynamicGridView(Context context, AttributeSet attrs, int defStyle) {
105 | super(context, attrs, defStyle);
106 | init(context);
107 | }
108 |
109 | @Override
110 | public void setOnScrollListener(OnScrollListener scrollListener) {
111 | this.mUserScrollListener = scrollListener;
112 | }
113 |
114 | public void setOnDropListener(OnDropListener dropListener) {
115 | this.mDropListener = dropListener;
116 | }
117 |
118 | public void setOnDragListener(OnDragListener dragListener) {
119 | this.mDragListener = dragListener;
120 | }
121 |
122 | /**
123 | * Start edit mode without starting drag;
124 | */
125 | public void startEditMode() {
126 | startEditMode(-1);
127 | }
128 |
129 | /**
130 | * Start edit mode with position. Useful for start edit mode in
131 | * {@link android.widget.AdapterView.OnItemClickListener}
132 | * or {@link android.widget.AdapterView.OnItemLongClickListener}
133 | */
134 | public void startEditMode(int position) {
135 | if (!mIsEditModeEnabled)
136 | return;
137 | requestDisallowInterceptTouchEvent(true);
138 | if (isPostHoneycomb() && mWobbleInEditMode)
139 | startWobbleAnimation();
140 | if (position != -1) {
141 | startDragAtPosition(position);
142 | }
143 | mIsEditMode = true;
144 | if (mEditModeChangeListener != null)
145 | mEditModeChangeListener.onEditModeChanged(true);
146 | }
147 |
148 | public void stopEditMode() {
149 | mIsEditMode = false;
150 | requestDisallowInterceptTouchEvent(false);
151 | if (isPostHoneycomb() && mWobbleInEditMode)
152 | stopWobble(true);
153 | if (mEditModeChangeListener != null)
154 | mEditModeChangeListener.onEditModeChanged(false);
155 | }
156 |
157 | public boolean isEditModeEnabled() {
158 | return mIsEditModeEnabled;
159 | }
160 |
161 | public void setEditModeEnabled(boolean enabled) {
162 | this.mIsEditModeEnabled = enabled;
163 | }
164 |
165 | public void setOnEditModeChangeListener(OnEditModeChangeListener editModeChangeListener) {
166 | this.mEditModeChangeListener = editModeChangeListener;
167 | }
168 |
169 | public boolean isEditMode() {
170 | return mIsEditMode;
171 | }
172 |
173 | public boolean isWobbleInEditMode() {
174 | return mWobbleInEditMode;
175 | }
176 |
177 | public void setWobbleInEditMode(boolean wobbleInEditMode) {
178 | this.mWobbleInEditMode = wobbleInEditMode;
179 | }
180 |
181 | @Override
182 | public void setOnItemClickListener(OnItemClickListener listener) {
183 | this.mUserItemClickListener = listener;
184 | super.setOnItemClickListener(mLocalItemClickListener);
185 | }
186 |
187 | public boolean isUndoSupportEnabled() {
188 | return mUndoSupportEnabled;
189 | }
190 |
191 | public void setUndoSupportEnabled(boolean undoSupportEnabled) {
192 | if (this.mUndoSupportEnabled != undoSupportEnabled) {
193 | if (undoSupportEnabled) {
194 | this.mModificationStack = new Stack();
195 | } else {
196 | this.mModificationStack = null;
197 | }
198 | }
199 |
200 | this.mUndoSupportEnabled = undoSupportEnabled;
201 | }
202 |
203 | public void undoLastModification() {
204 | if (mUndoSupportEnabled) {
205 | if (mModificationStack != null && !mModificationStack.isEmpty()) {
206 | DynamicGridModification modification = mModificationStack.pop();
207 | undoModification(modification);
208 | }
209 | }
210 | }
211 |
212 | public void undoAllModifications() {
213 | if (mUndoSupportEnabled) {
214 | if (mModificationStack != null && !mModificationStack.isEmpty()) {
215 | while (!mModificationStack.isEmpty()) {
216 | DynamicGridModification modification = mModificationStack.pop();
217 | undoModification(modification);
218 | }
219 | }
220 | }
221 | }
222 |
223 | public boolean hasModificationHistory() {
224 | if (mUndoSupportEnabled) {
225 | if (mModificationStack != null && !mModificationStack.isEmpty()) {
226 | return true;
227 | }
228 | }
229 | return false;
230 | }
231 |
232 | public void clearModificationHistory() {
233 | mModificationStack.clear();
234 | }
235 |
236 | public void setOnSelectedItemBitmapCreationListener(OnSelectedItemBitmapCreationListener selectedItemBitmapCreationListener) {
237 | this.mSelectedItemBitmapCreationListener = selectedItemBitmapCreationListener;
238 | }
239 |
240 | private void undoModification(DynamicGridModification modification) {
241 | for (Pair transition : modification.getTransitions()) {
242 | reorderElements(transition.second, transition.first);
243 | }
244 | }
245 |
246 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
247 | private void startWobbleAnimation() {
248 | for (int i = 0; i < getChildCount(); i++) {
249 | View v = getChildAt(i);
250 | if (v != null && Boolean.TRUE != v.getTag(R.id.dgv_wobble_tag)) {
251 | if (i % 2 == 0)
252 | animateWobble(v);
253 | else
254 | animateWobbleInverse(v);
255 | v.setTag(R.id.dgv_wobble_tag, true);
256 | }
257 | }
258 | }
259 |
260 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
261 | private void stopWobble(boolean resetRotation) {
262 | for (Animator wobbleAnimator : mWobbleAnimators) {
263 | wobbleAnimator.cancel();
264 | }
265 | mWobbleAnimators.clear();
266 | for (int i = 0; i < getChildCount(); i++) {
267 | View v = getChildAt(i);
268 | if (v != null) {
269 | if (resetRotation) v.setRotation(0);
270 | v.setTag(R.id.dgv_wobble_tag, false);
271 | }
272 | }
273 | }
274 |
275 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
276 | private void restartWobble() {
277 | stopWobble(false);
278 | startWobbleAnimation();
279 | }
280 |
281 | public void init(Context context) {
282 | super.setOnScrollListener(mScrollListener);
283 | DisplayMetrics metrics = context.getResources().getDisplayMetrics();
284 | mSmoothScrollAmountAtEdge = (int) (SMOOTH_SCROLL_AMOUNT_AT_EDGE * metrics.density + 0.5f);
285 | mOverlapIfSwitchStraightLine = getResources().getDimensionPixelSize(R.dimen.dgv_overlap_if_switch_straight_line);
286 | }
287 |
288 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
289 | private void animateWobble(View v) {
290 | ObjectAnimator animator = createBaseWobble(v);
291 | animator.setFloatValues(-2, 2);
292 | animator.start();
293 | mWobbleAnimators.add(animator);
294 | }
295 |
296 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
297 | private void animateWobbleInverse(View v) {
298 | ObjectAnimator animator = createBaseWobble(v);
299 | animator.setFloatValues(2, -2);
300 | animator.start();
301 | mWobbleAnimators.add(animator);
302 | }
303 |
304 |
305 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
306 | private ObjectAnimator createBaseWobble(final View v) {
307 |
308 | if (!isPreLollipop())
309 | v.setLayerType(LAYER_TYPE_SOFTWARE, null);
310 |
311 | ObjectAnimator animator = new ObjectAnimator();
312 | animator.setDuration(180);
313 | animator.setRepeatMode(ValueAnimator.REVERSE);
314 | animator.setRepeatCount(ValueAnimator.INFINITE);
315 | animator.setPropertyName("rotation");
316 | animator.setTarget(v);
317 | animator.addListener(new AnimatorListenerAdapter() {
318 | @Override
319 | public void onAnimationEnd(Animator animation) {
320 | v.setLayerType(LAYER_TYPE_NONE, null);
321 | }
322 | });
323 | return animator;
324 | }
325 |
326 |
327 | private void reorderElements(int originalPosition, int targetPosition) {
328 | if (mDragListener != null)
329 | mDragListener.onDragPositionsChanged(originalPosition, targetPosition);
330 | getAdapterInterface().reorderItems(originalPosition, targetPosition);
331 | }
332 |
333 | private int getColumnCount() {
334 | return getAdapterInterface().getColumnCount();
335 | }
336 |
337 | private DynamicGridAdapterInterface getAdapterInterface() {
338 | return ((DynamicGridAdapterInterface) getAdapter());
339 | }
340 |
341 | /**
342 | * Creates the hover cell with the appropriate bitmap and of appropriate
343 | * size. The hover cell's BitmapDrawable is drawn on top of the bitmap every
344 | * single time an invalidate call is made.
345 | */
346 | private BitmapDrawable getAndAddHoverView(View v) {
347 |
348 | int w = v.getWidth();
349 | int h = v.getHeight();
350 | int top = v.getTop();
351 | int left = v.getLeft();
352 |
353 | Bitmap b = getBitmapFromView(v);
354 |
355 | BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
356 |
357 | mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
358 | mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds);
359 |
360 | drawable.setBounds(mHoverCellCurrentBounds);
361 |
362 | return drawable;
363 | }
364 |
365 | /**
366 | * Returns a bitmap showing a screenshot of the view passed in.
367 | */
368 | private Bitmap getBitmapFromView(View v) {
369 | Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
370 | Canvas canvas = new Canvas(bitmap);
371 | v.draw(canvas);
372 | return bitmap;
373 | }
374 |
375 |
376 | private void updateNeighborViewsForId(long itemId) {
377 | idList.clear();
378 | int draggedPos = getPositionForID(itemId);
379 | for (int pos = getFirstVisiblePosition(); pos <= getLastVisiblePosition(); pos++) {
380 | if (draggedPos != pos && getAdapterInterface().canReorder(pos)) {
381 | idList.add(getId(pos));
382 | }
383 | }
384 | }
385 |
386 | /**
387 | * Retrieves the position in the grid corresponding to itemId
388 | */
389 | public int getPositionForID(long itemId) {
390 | View v = getViewForId(itemId);
391 | if (v == null) {
392 | return -1;
393 | } else {
394 | return getPositionForView(v);
395 | }
396 | }
397 |
398 | public View getViewForId(long itemId) {
399 | int firstVisiblePosition = getFirstVisiblePosition();
400 | ListAdapter adapter = getAdapter();
401 | for (int i = 0; i < getChildCount(); i++) {
402 | View v = getChildAt(i);
403 | int position = firstVisiblePosition + i;
404 | long id = adapter.getItemId(position);
405 | if (id == itemId) {
406 | return v;
407 | }
408 | }
409 | return null;
410 | }
411 |
412 | @Override
413 | public boolean onTouchEvent(MotionEvent event) {
414 | switch (event.getAction() & MotionEvent.ACTION_MASK) {
415 | case MotionEvent.ACTION_DOWN:
416 | mDownX = (int) event.getX();
417 | mDownY = (int) event.getY();
418 | mActivePointerId = event.getPointerId(0);
419 | if (mIsEditMode && isEnabled()) {
420 | layoutChildren();
421 | int position = pointToPosition(mDownX, mDownY);
422 | startDragAtPosition(position);
423 | } else if (!isEnabled()) {
424 | return false;
425 | }
426 |
427 | break;
428 |
429 | case MotionEvent.ACTION_MOVE:
430 | if (mActivePointerId == INVALID_ID) {
431 | break;
432 | }
433 |
434 | int pointerIndex = event.findPointerIndex(mActivePointerId);
435 |
436 | mLastEventY = (int) event.getY(pointerIndex);
437 | mLastEventX = (int) event.getX(pointerIndex);
438 | int deltaY = mLastEventY - mDownY;
439 | int deltaX = mLastEventX - mDownX;
440 |
441 | if (mCellIsMobile) {
442 | mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left + deltaX + mTotalOffsetX,
443 | mHoverCellOriginalBounds.top + deltaY + mTotalOffsetY);
444 | mHoverCell.setBounds(mHoverCellCurrentBounds);
445 | invalidate();
446 | handleCellSwitch();
447 | mIsMobileScrolling = false;
448 | handleMobileCellScroll();
449 | return false;
450 | }
451 | break;
452 |
453 | case MotionEvent.ACTION_UP:
454 | touchEventsEnded();
455 |
456 | if (mUndoSupportEnabled) {
457 | if (mCurrentModification != null && !mCurrentModification.getTransitions().isEmpty()) {
458 | mModificationStack.push(mCurrentModification);
459 | mCurrentModification = new DynamicGridModification();
460 | }
461 | }
462 |
463 | if (mHoverCell != null) {
464 | if (mDropListener != null) {
465 | mDropListener.onActionDrop();
466 | }
467 | }
468 | break;
469 |
470 | case MotionEvent.ACTION_CANCEL:
471 | touchEventsCancelled();
472 |
473 | if (mHoverCell != null) {
474 | if (mDropListener != null) {
475 | mDropListener.onActionDrop();
476 | }
477 | }
478 | break;
479 |
480 | case MotionEvent.ACTION_POINTER_UP:
481 | /* If a multitouch event took place and the original touch dictating
482 | * the movement of the hover cell has ended, then the dragging event
483 | * ends and the hover cell is animated to its corresponding position
484 | * in the listview. */
485 | pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
486 | MotionEvent.ACTION_POINTER_INDEX_SHIFT;
487 | final int pointerId = event.getPointerId(pointerIndex);
488 | if (pointerId == mActivePointerId) {
489 | touchEventsEnded();
490 | }
491 | break;
492 |
493 | default:
494 | break;
495 | }
496 |
497 | return super.onTouchEvent(event);
498 | }
499 |
500 | private void startDragAtPosition(int position) {
501 | mTotalOffsetY = 0;
502 | mTotalOffsetX = 0;
503 | int itemNum = position - getFirstVisiblePosition();
504 | View selectedView = getChildAt(itemNum);
505 | if (selectedView != null) {
506 | mMobileItemId = getAdapter().getItemId(position);
507 | if (mSelectedItemBitmapCreationListener != null)
508 | mSelectedItemBitmapCreationListener.onPreSelectedItemBitmapCreation(selectedView, position, mMobileItemId);
509 | mHoverCell = getAndAddHoverView(selectedView);
510 | if (mSelectedItemBitmapCreationListener != null)
511 | mSelectedItemBitmapCreationListener.onPostSelectedItemBitmapCreation(selectedView, position, mMobileItemId);
512 | if (isPostHoneycomb())
513 | selectedView.setVisibility(View.INVISIBLE);
514 | mCellIsMobile = true;
515 | updateNeighborViewsForId(mMobileItemId);
516 | if (mDragListener != null) {
517 | mDragListener.onDragStarted(position);
518 | }
519 | }
520 | }
521 |
522 | private void handleMobileCellScroll() {
523 | mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds);
524 | }
525 |
526 | public boolean handleMobileCellScroll(Rect r) {
527 | int offset = computeVerticalScrollOffset();
528 | int height = getHeight();
529 | int extent = computeVerticalScrollExtent();
530 | int range = computeVerticalScrollRange();
531 | int hoverViewTop = r.top;
532 | int hoverHeight = r.height();
533 |
534 | if (hoverViewTop <= 0 && offset > 0) {
535 | smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
536 | return true;
537 | }
538 |
539 | if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) {
540 | smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
541 | return true;
542 | }
543 |
544 | return false;
545 | }
546 |
547 | @Override
548 | public void setAdapter(ListAdapter adapter) {
549 | super.setAdapter(adapter);
550 | }
551 |
552 | private void touchEventsEnded() {
553 | final View mobileView = getViewForId(mMobileItemId);
554 | if (mobileView != null && (mCellIsMobile || mIsWaitingForScrollFinish)) {
555 | mCellIsMobile = false;
556 | mIsWaitingForScrollFinish = false;
557 | mIsMobileScrolling = false;
558 | mActivePointerId = INVALID_ID;
559 |
560 | // If the autoscroller has not completed scrolling, we need to wait for it to
561 | // finish in order to determine the final location of where the hover cell
562 | // should be animated to.
563 | if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
564 | mIsWaitingForScrollFinish = true;
565 | return;
566 | }
567 |
568 | mHoverCellCurrentBounds.offsetTo(mobileView.getLeft(), mobileView.getTop());
569 |
570 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
571 | animateBounds(mobileView);
572 | } else {
573 | mHoverCell.setBounds(mHoverCellCurrentBounds);
574 | invalidate();
575 | reset(mobileView);
576 | }
577 | } else {
578 | touchEventsCancelled();
579 | }
580 | }
581 |
582 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
583 | private void animateBounds(final View mobileView) {
584 | TypeEvaluator sBoundEvaluator = new TypeEvaluator() {
585 | public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
586 | return new Rect(interpolate(startValue.left, endValue.left, fraction),
587 | interpolate(startValue.top, endValue.top, fraction),
588 | interpolate(startValue.right, endValue.right, fraction),
589 | interpolate(startValue.bottom, endValue.bottom, fraction));
590 | }
591 |
592 | public int interpolate(int start, int end, float fraction) {
593 | return (int) (start + fraction * (end - start));
594 | }
595 | };
596 |
597 |
598 | ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds",
599 | sBoundEvaluator, mHoverCellCurrentBounds);
600 | hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
601 | @Override
602 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
603 | invalidate();
604 | }
605 | });
606 | hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
607 | @Override
608 | public void onAnimationStart(Animator animation) {
609 | mHoverAnimation = true;
610 | updateEnableState();
611 | }
612 |
613 | @Override
614 | public void onAnimationEnd(Animator animation) {
615 | mHoverAnimation = false;
616 | updateEnableState();
617 | reset(mobileView);
618 | }
619 | });
620 | hoverViewAnimator.start();
621 | }
622 |
623 | private void reset(View mobileView) {
624 | idList.clear();
625 | mMobileItemId = INVALID_ID;
626 | mobileView.setVisibility(View.VISIBLE);
627 | mHoverCell = null;
628 | if (isPostHoneycomb() && mWobbleInEditMode) {
629 | if (mIsEditMode) {
630 | restartWobble();
631 | } else{
632 | stopWobble(true);
633 | }
634 | }
635 | //ugly fix for unclear disappearing items after reorder
636 | for (int i = 0; i < getLastVisiblePosition() - getFirstVisiblePosition(); i++) {
637 | View child = getChildAt(i);
638 | if (child != null) {
639 | child.setVisibility(View.VISIBLE);
640 | }
641 | }
642 | invalidate();
643 | }
644 |
645 | private void updateEnableState() {
646 | setEnabled(!mHoverAnimation && !mReorderAnimation);
647 | }
648 |
649 | /**
650 | * Seems that GridView before HONEYCOMB not support stable id in proper way.
651 | * That cause bugs on view recycle if we will animate or change visibility state for items.
652 | *
653 | * @return
654 | */
655 | private boolean isPostHoneycomb() {
656 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
657 | }
658 |
659 | /**
660 | * The GridView from Android Lollipoop requires some different
661 | * setVisibility() logic when switching cells.
662 | *
663 | * @return true if OS version is less than Lollipop, false if not
664 | */
665 | public static boolean isPreLollipop() {
666 | return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
667 | }
668 |
669 | private void touchEventsCancelled() {
670 | View mobileView = getViewForId(mMobileItemId);
671 | if (mCellIsMobile) {
672 | reset(mobileView);
673 | }
674 | mCellIsMobile = false;
675 | mIsMobileScrolling = false;
676 | mActivePointerId = INVALID_ID;
677 |
678 | }
679 |
680 | private void handleCellSwitch() {
681 | final int deltaY = mLastEventY - mDownY;
682 | final int deltaX = mLastEventX - mDownX;
683 | final int deltaYTotal = mHoverCellOriginalBounds.centerY() + mTotalOffsetY + deltaY;
684 | final int deltaXTotal = mHoverCellOriginalBounds.centerX() + mTotalOffsetX + deltaX;
685 | mMobileView = getViewForId(mMobileItemId);
686 | View targetView = null;
687 | float vX = 0;
688 | float vY = 0;
689 | Point mobileColumnRowPair = getColumnAndRowForView(mMobileView);
690 | for (Long id : idList) {
691 | View view = getViewForId(id);
692 | if (view != null) {
693 | Point targetColumnRowPair = getColumnAndRowForView(view);
694 | if ((aboveRight(targetColumnRowPair, mobileColumnRowPair)
695 | && deltaYTotal < view.getBottom() && deltaXTotal > view.getLeft()
696 | || aboveLeft(targetColumnRowPair, mobileColumnRowPair)
697 | && deltaYTotal < view.getBottom() && deltaXTotal < view.getRight()
698 | || belowRight(targetColumnRowPair, mobileColumnRowPair)
699 | && deltaYTotal > view.getTop() && deltaXTotal > view.getLeft()
700 | || belowLeft(targetColumnRowPair, mobileColumnRowPair)
701 | && deltaYTotal > view.getTop() && deltaXTotal < view.getRight()
702 | || above(targetColumnRowPair, mobileColumnRowPair)
703 | && deltaYTotal < view.getBottom() - mOverlapIfSwitchStraightLine
704 | || below(targetColumnRowPair, mobileColumnRowPair)
705 | && deltaYTotal > view.getTop() + mOverlapIfSwitchStraightLine
706 | || right(targetColumnRowPair, mobileColumnRowPair)
707 | && deltaXTotal > view.getLeft() + mOverlapIfSwitchStraightLine
708 | || left(targetColumnRowPair, mobileColumnRowPair)
709 | && deltaXTotal < view.getRight() - mOverlapIfSwitchStraightLine)) {
710 | float xDiff = Math.abs(DynamicGridUtils.getViewX(view) - DynamicGridUtils.getViewX(mMobileView));
711 | float yDiff = Math.abs(DynamicGridUtils.getViewY(view) - DynamicGridUtils.getViewY(mMobileView));
712 | if (xDiff >= vX && yDiff >= vY) {
713 | vX = xDiff;
714 | vY = yDiff;
715 | targetView = view;
716 | }
717 | }
718 | }
719 | }
720 | if (targetView != null) {
721 | final int originalPosition = getPositionForView(mMobileView);
722 | int targetPosition = getPositionForView(targetView);
723 |
724 | final DynamicGridAdapterInterface adapter = getAdapterInterface();
725 | if (targetPosition == INVALID_POSITION || !adapter.canReorder(originalPosition) || !adapter.canReorder(targetPosition)) {
726 | updateNeighborViewsForId(mMobileItemId);
727 | return;
728 | }
729 | reorderElements(originalPosition, targetPosition);
730 |
731 | if (mUndoSupportEnabled) {
732 | mCurrentModification.addTransition(originalPosition, targetPosition);
733 | }
734 |
735 | mDownY = mLastEventY;
736 | mDownX = mLastEventX;
737 |
738 | SwitchCellAnimator switchCellAnimator;
739 |
740 | if (isPostHoneycomb() && isPreLollipop()) //Between Android 3.0 and Android L
741 | switchCellAnimator = new KitKatSwitchCellAnimator(deltaX, deltaY);
742 | else if (isPreLollipop()) //Before Android 3.0
743 | switchCellAnimator = new PreHoneycombCellAnimator(deltaX, deltaY);
744 | else //Android L
745 | switchCellAnimator = new LSwitchCellAnimator(deltaX, deltaY);
746 |
747 | updateNeighborViewsForId(mMobileItemId);
748 |
749 | switchCellAnimator.animateSwitchCell(originalPosition, targetPosition);
750 | }
751 | }
752 |
753 | private interface SwitchCellAnimator {
754 | void animateSwitchCell(final int originalPosition, final int targetPosition);
755 | }
756 |
757 | private class PreHoneycombCellAnimator implements SwitchCellAnimator {
758 | private int mDeltaY;
759 | private int mDeltaX;
760 |
761 | public PreHoneycombCellAnimator(int deltaX, int deltaY) {
762 | mDeltaX = deltaX;
763 | mDeltaY = deltaY;
764 | }
765 |
766 | @Override
767 | public void animateSwitchCell(int originalPosition, int targetPosition) {
768 | mTotalOffsetY += mDeltaY;
769 | mTotalOffsetX += mDeltaX;
770 | }
771 | }
772 |
773 | /**
774 | * A {@link org.askerov.dynamicgrid.DynamicGridView.SwitchCellAnimator} for versions KitKat and below.
775 | */
776 | private class KitKatSwitchCellAnimator implements SwitchCellAnimator {
777 |
778 | private int mDeltaY;
779 | private int mDeltaX;
780 |
781 | public KitKatSwitchCellAnimator(int deltaX, int deltaY) {
782 | mDeltaX = deltaX;
783 | mDeltaY = deltaY;
784 | }
785 |
786 | @Override
787 | public void animateSwitchCell(final int originalPosition, final int targetPosition) {
788 | assert mMobileView != null;
789 | getViewTreeObserver().addOnPreDrawListener(new AnimateSwitchViewOnPreDrawListener(mMobileView, originalPosition, targetPosition));
790 | mMobileView = getViewForId(mMobileItemId);
791 | }
792 |
793 | private class AnimateSwitchViewOnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
794 |
795 | private final View mPreviousMobileView;
796 | private final int mOriginalPosition;
797 | private final int mTargetPosition;
798 |
799 | AnimateSwitchViewOnPreDrawListener(final View previousMobileView, final int originalPosition, final int targetPosition) {
800 | mPreviousMobileView = previousMobileView;
801 | mOriginalPosition = originalPosition;
802 | mTargetPosition = targetPosition;
803 | }
804 |
805 | @Override
806 | public boolean onPreDraw() {
807 | getViewTreeObserver().removeOnPreDrawListener(this);
808 |
809 | mTotalOffsetY += mDeltaY;
810 | mTotalOffsetX += mDeltaX;
811 |
812 | animateReorder(mOriginalPosition, mTargetPosition);
813 |
814 | mPreviousMobileView.setVisibility(View.VISIBLE);
815 |
816 | if (mMobileView != null) {
817 | mMobileView.setVisibility(View.INVISIBLE);
818 | }
819 | return true;
820 | }
821 | }
822 | }
823 |
824 | /**
825 | * A {@link org.askerov.dynamicgrid.DynamicGridView.SwitchCellAnimator} for versions L and above.
826 | */
827 | private class LSwitchCellAnimator implements SwitchCellAnimator {
828 |
829 | private int mDeltaY;
830 | private int mDeltaX;
831 |
832 | public LSwitchCellAnimator(int deltaX, int deltaY) {
833 | mDeltaX = deltaX;
834 | mDeltaY = deltaY;
835 | }
836 |
837 | @Override
838 | public void animateSwitchCell(final int originalPosition, final int targetPosition) {
839 | getViewTreeObserver().addOnPreDrawListener(new AnimateSwitchViewOnPreDrawListener(originalPosition, targetPosition));
840 | }
841 |
842 | private class AnimateSwitchViewOnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
843 | private final int mOriginalPosition;
844 | private final int mTargetPosition;
845 |
846 | AnimateSwitchViewOnPreDrawListener(final int originalPosition, final int targetPosition) {
847 | mOriginalPosition = originalPosition;
848 | mTargetPosition = targetPosition;
849 | }
850 |
851 | @Override
852 | public boolean onPreDraw() {
853 | getViewTreeObserver().removeOnPreDrawListener(this);
854 |
855 | mTotalOffsetY += mDeltaY;
856 | mTotalOffsetX += mDeltaX;
857 |
858 | animateReorder(mOriginalPosition, mTargetPosition);
859 |
860 | assert mMobileView != null;
861 | mMobileView.setVisibility(View.VISIBLE);
862 | mMobileView = getViewForId(mMobileItemId);
863 | assert mMobileView != null;
864 | mMobileView.setVisibility(View.INVISIBLE);
865 | return true;
866 | }
867 | }
868 | }
869 |
870 | private boolean belowLeft(Point targetColumnRowPair, Point mobileColumnRowPair) {
871 | return targetColumnRowPair.y > mobileColumnRowPair.y && targetColumnRowPair.x < mobileColumnRowPair.x;
872 | }
873 |
874 | private boolean belowRight(Point targetColumnRowPair, Point mobileColumnRowPair) {
875 | return targetColumnRowPair.y > mobileColumnRowPair.y && targetColumnRowPair.x > mobileColumnRowPair.x;
876 | }
877 |
878 | private boolean aboveLeft(Point targetColumnRowPair, Point mobileColumnRowPair) {
879 | return targetColumnRowPair.y < mobileColumnRowPair.y && targetColumnRowPair.x < mobileColumnRowPair.x;
880 | }
881 |
882 | private boolean aboveRight(Point targetColumnRowPair, Point mobileColumnRowPair) {
883 | return targetColumnRowPair.y < mobileColumnRowPair.y && targetColumnRowPair.x > mobileColumnRowPair.x;
884 | }
885 |
886 | private boolean above(Point targetColumnRowPair, Point mobileColumnRowPair) {
887 | return targetColumnRowPair.y < mobileColumnRowPair.y && targetColumnRowPair.x == mobileColumnRowPair.x;
888 | }
889 |
890 | private boolean below(Point targetColumnRowPair, Point mobileColumnRowPair) {
891 | return targetColumnRowPair.y > mobileColumnRowPair.y && targetColumnRowPair.x == mobileColumnRowPair.x;
892 | }
893 |
894 | private boolean right(Point targetColumnRowPair, Point mobileColumnRowPair) {
895 | return targetColumnRowPair.y == mobileColumnRowPair.y && targetColumnRowPair.x > mobileColumnRowPair.x;
896 | }
897 |
898 | private boolean left(Point targetColumnRowPair, Point mobileColumnRowPair) {
899 | return targetColumnRowPair.y == mobileColumnRowPair.y && targetColumnRowPair.x < mobileColumnRowPair.x;
900 | }
901 |
902 | private Point getColumnAndRowForView(View view) {
903 | int pos = getPositionForView(view);
904 | int columns = getColumnCount();
905 | int column = pos % columns;
906 | int row = pos / columns;
907 | return new Point(column, row);
908 | }
909 |
910 | private long getId(int position) {
911 | return getAdapter().getItemId(position);
912 | }
913 |
914 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
915 | private void animateReorder(final int oldPosition, final int newPosition) {
916 | boolean isForward = newPosition > oldPosition;
917 | List resultList = new LinkedList();
918 | if (isForward) {
919 | for (int pos = Math.min(oldPosition, newPosition); pos < Math.max(oldPosition, newPosition); pos++) {
920 | View view = getViewForId(getId(pos));
921 | if ((pos + 1) % getColumnCount() == 0) {
922 | resultList.add(createTranslationAnimations(view, -view.getWidth() * (getColumnCount() - 1), 0,
923 | view.getHeight(), 0));
924 | } else {
925 | resultList.add(createTranslationAnimations(view, view.getWidth(), 0, 0, 0));
926 | }
927 | }
928 | } else {
929 | for (int pos = Math.max(oldPosition, newPosition); pos > Math.min(oldPosition, newPosition); pos--) {
930 | View view = getViewForId(getId(pos));
931 | if ((pos + getColumnCount()) % getColumnCount() == 0) {
932 | resultList.add(createTranslationAnimations(view, view.getWidth() * (getColumnCount() - 1), 0,
933 | -view.getHeight(), 0));
934 | } else {
935 | resultList.add(createTranslationAnimations(view, -view.getWidth(), 0, 0, 0));
936 | }
937 | }
938 | }
939 |
940 | AnimatorSet resultSet = new AnimatorSet();
941 | resultSet.playTogether(resultList);
942 | resultSet.setDuration(MOVE_DURATION);
943 | resultSet.setInterpolator(new AccelerateDecelerateInterpolator());
944 | resultSet.addListener(new AnimatorListenerAdapter() {
945 | @Override
946 | public void onAnimationStart(Animator animation) {
947 | mReorderAnimation = true;
948 | updateEnableState();
949 | }
950 |
951 | @Override
952 | public void onAnimationEnd(Animator animation) {
953 | mReorderAnimation = false;
954 | updateEnableState();
955 | }
956 | });
957 | resultSet.start();
958 | }
959 |
960 |
961 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
962 | private AnimatorSet createTranslationAnimations(View view, float startX, float endX, float startY, float endY) {
963 | ObjectAnimator animX = ObjectAnimator.ofFloat(view, "translationX", startX, endX);
964 | ObjectAnimator animY = ObjectAnimator.ofFloat(view, "translationY", startY, endY);
965 | AnimatorSet animSetXY = new AnimatorSet();
966 | animSetXY.playTogether(animX, animY);
967 | return animSetXY;
968 | }
969 |
970 | @Override
971 | protected void dispatchDraw(Canvas canvas) {
972 | super.dispatchDraw(canvas);
973 | if (mHoverCell != null) {
974 | mHoverCell.draw(canvas);
975 | }
976 | }
977 |
978 |
979 | public interface OnDropListener {
980 | void onActionDrop();
981 | }
982 |
983 | public interface OnDragListener {
984 |
985 | public void onDragStarted(int position);
986 |
987 | public void onDragPositionsChanged(int oldPosition, int newPosition);
988 | }
989 |
990 | public interface OnEditModeChangeListener {
991 | public void onEditModeChanged(boolean inEditMode);
992 | }
993 |
994 | public interface OnSelectedItemBitmapCreationListener {
995 | public void onPreSelectedItemBitmapCreation(View selectedView, int position, long itemId);
996 |
997 | public void onPostSelectedItemBitmapCreation(View selectedView, int position, long itemId);
998 | }
999 |
1000 |
1001 | /**
1002 | * This scroll listener is added to the gridview in order to handle cell swapping
1003 | * when the cell is either at the top or bottom edge of the gridview. If the hover
1004 | * cell is at either edge of the gridview, the gridview will begin scrolling. As
1005 | * scrolling takes place, the gridview continuously checks if new cells became visible
1006 | * and determines whether they are potential candidates for a cell swap.
1007 | */
1008 | private OnScrollListener mScrollListener = new OnScrollListener() {
1009 |
1010 | private int mPreviousFirstVisibleItem = -1;
1011 | private int mPreviousVisibleItemCount = -1;
1012 | private int mCurrentFirstVisibleItem;
1013 | private int mCurrentVisibleItemCount;
1014 | private int mCurrentScrollState;
1015 |
1016 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1017 | int totalItemCount) {
1018 | mCurrentFirstVisibleItem = firstVisibleItem;
1019 | mCurrentVisibleItemCount = visibleItemCount;
1020 |
1021 | mPreviousFirstVisibleItem = (mPreviousFirstVisibleItem == -1) ? mCurrentFirstVisibleItem
1022 | : mPreviousFirstVisibleItem;
1023 | mPreviousVisibleItemCount = (mPreviousVisibleItemCount == -1) ? mCurrentVisibleItemCount
1024 | : mPreviousVisibleItemCount;
1025 |
1026 | checkAndHandleFirstVisibleCellChange();
1027 | checkAndHandleLastVisibleCellChange();
1028 |
1029 | mPreviousFirstVisibleItem = mCurrentFirstVisibleItem;
1030 | mPreviousVisibleItemCount = mCurrentVisibleItemCount;
1031 | if (isPostHoneycomb() && mWobbleInEditMode) {
1032 | updateWobbleState(visibleItemCount);
1033 | }
1034 | if (mUserScrollListener != null) {
1035 | mUserScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
1036 | }
1037 | }
1038 |
1039 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
1040 | private void updateWobbleState(int visibleItemCount) {
1041 | for (int i = 0; i < visibleItemCount; i++) {
1042 | View child = getChildAt(i);
1043 |
1044 | if (child != null) {
1045 | if (mMobileItemId != INVALID_ID && Boolean.TRUE != child.getTag(R.id.dgv_wobble_tag)) {
1046 | if (i % 2 == 0)
1047 | animateWobble(child);
1048 | else
1049 | animateWobbleInverse(child);
1050 | child.setTag(R.id.dgv_wobble_tag, true);
1051 | } else if (mMobileItemId == INVALID_ID && child.getRotation() != 0) {
1052 | child.setRotation(0);
1053 | child.setTag(R.id.dgv_wobble_tag, false);
1054 | }
1055 | }
1056 |
1057 | }
1058 | }
1059 |
1060 | @Override
1061 | public void onScrollStateChanged(AbsListView view, int scrollState) {
1062 | mCurrentScrollState = scrollState;
1063 | mScrollState = scrollState;
1064 | isScrollCompleted();
1065 | if (mUserScrollListener != null) {
1066 | mUserScrollListener.onScrollStateChanged(view, scrollState);
1067 | }
1068 | }
1069 |
1070 | /**
1071 | * This method is in charge of invoking 1 of 2 actions. Firstly, if the gridview
1072 | * is in a state of scrolling invoked by the hover cell being outside the bounds
1073 | * of the gridview, then this scrolling event is continued. Secondly, if the hover
1074 | * cell has already been released, this invokes the animation for the hover cell
1075 | * to return to its correct position after the gridview has entered an idle scroll
1076 | * state.
1077 | */
1078 | private void isScrollCompleted() {
1079 | if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) {
1080 | if (mCellIsMobile && mIsMobileScrolling) {
1081 | handleMobileCellScroll();
1082 | } else if (mIsWaitingForScrollFinish) {
1083 | touchEventsEnded();
1084 | }
1085 | }
1086 | }
1087 |
1088 | /**
1089 | * Determines if the gridview scrolled up enough to reveal a new cell at the
1090 | * top of the list. If so, then the appropriate parameters are updated.
1091 | */
1092 | public void checkAndHandleFirstVisibleCellChange() {
1093 | if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) {
1094 | if (mCellIsMobile && mMobileItemId != INVALID_ID) {
1095 | updateNeighborViewsForId(mMobileItemId);
1096 | handleCellSwitch();
1097 | }
1098 | }
1099 | }
1100 |
1101 | /**
1102 | * Determines if the gridview scrolled down enough to reveal a new cell at the
1103 | * bottom of the list. If so, then the appropriate parameters are updated.
1104 | */
1105 | public void checkAndHandleLastVisibleCellChange() {
1106 | int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount;
1107 | int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount;
1108 | if (currentLastVisibleItem != previousLastVisibleItem) {
1109 | if (mCellIsMobile && mMobileItemId != INVALID_ID) {
1110 | updateNeighborViewsForId(mMobileItemId);
1111 | handleCellSwitch();
1112 | }
1113 | }
1114 | }
1115 | };
1116 |
1117 | private static class DynamicGridModification {
1118 |
1119 | private List> transitions;
1120 |
1121 | DynamicGridModification() {
1122 | super();
1123 | this.transitions = new Stack>();
1124 | }
1125 |
1126 | public boolean hasTransitions() {
1127 | return !transitions.isEmpty();
1128 | }
1129 |
1130 | public void addTransition(int oldPosition, int newPosition) {
1131 | transitions.add(new Pair(oldPosition, newPosition));
1132 | }
1133 |
1134 | public List> getTransitions() {
1135 | Collections.reverse(transitions);
1136 | return transitions;
1137 | }
1138 | }
1139 | }
1140 |
1141 |
--------------------------------------------------------------------------------
/example/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/example/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | dependencies {
4 | compile project(':dynamicgrid')
5 | compile 'com.android.support:support-v4:19.+'
6 | }
7 |
8 | android {
9 | compileSdkVersion 21
10 | buildToolsVersion "21.1.1"
11 |
12 | sourceSets {
13 | main {
14 | manifest.srcFile 'AndroidManifest.xml'
15 | java.srcDirs = ['src']
16 | resources.srcDirs = ['src']
17 | aidl.srcDirs = ['src']
18 | renderscript.srcDirs = ['src']
19 | res.srcDirs = ['res']
20 | assets.srcDirs = ['assets']
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/example/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/askerov/DynamicGrid/f81c02853580a3820293fab4812cd441ca27ed25/example/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/askerov/DynamicGrid/f81c02853580a3820293fab4812cd441ca27ed25/example/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/askerov/DynamicGrid/f81c02853580a3820293fab4812cd441ca27ed25/example/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/askerov/DynamicGrid/f81c02853580a3820293fab4812cd441ca27ed25/example/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/res/layout/activity_grid.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/example/res/layout/item_grid.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
18 |
19 |
28 |
29 |
--------------------------------------------------------------------------------
/example/res/values/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 3
4 |
--------------------------------------------------------------------------------
/example/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | DynamicGridExample
4 |
5 |
--------------------------------------------------------------------------------
/example/src/org/askerov/dynamicgrid/example/CheeseDynamicAdapter.java:
--------------------------------------------------------------------------------
1 | package org.askerov.dynamicgrid.example;
2 |
3 | /**
4 | * Author: alex askerov
5 | * Date: 9/9/13
6 | * Time: 10:52 PM
7 | */
8 |
9 | import android.content.Context;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.widget.ImageView;
14 | import android.widget.TextView;
15 | import org.askerov.dynamicgrid.BaseDynamicGridAdapter;
16 |
17 | import java.util.List;
18 |
19 | /**
20 | * Author: alex askerov
21 | * Date: 9/7/13
22 | * Time: 10:56 PM
23 | */
24 | public class CheeseDynamicAdapter extends BaseDynamicGridAdapter {
25 | public CheeseDynamicAdapter(Context context, List> items, int columnCount) {
26 | super(context, items, columnCount);
27 | }
28 |
29 | @Override
30 | public View getView(int position, View convertView, ViewGroup parent) {
31 | CheeseViewHolder holder;
32 | if (convertView == null) {
33 | convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_grid, null);
34 | holder = new CheeseViewHolder(convertView);
35 | convertView.setTag(holder);
36 | } else {
37 | holder = (CheeseViewHolder) convertView.getTag();
38 | }
39 | holder.build(getItem(position).toString());
40 | return convertView;
41 | }
42 |
43 | private class CheeseViewHolder {
44 | private TextView titleText;
45 | private ImageView image;
46 |
47 | private CheeseViewHolder(View view) {
48 | titleText = (TextView) view.findViewById(R.id.item_title);
49 | image = (ImageView) view.findViewById(R.id.item_img);
50 | }
51 |
52 | void build(String title) {
53 | titleText.setText(title);
54 | image.setImageResource(R.drawable.ic_launcher);
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/example/src/org/askerov/dynamicgrid/example/Cheeses.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.askerov.dynamicgrid.example;
18 |
19 | public class Cheeses {
20 |
21 | public static final String[] sCheeseStrings = {
22 | "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
23 | "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
24 | "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
25 | "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
26 | "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
27 | "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
28 | "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
29 | "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
30 | "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
31 | "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
32 | "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
33 | "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
34 | "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
35 | "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
36 | "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
37 | "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
38 | "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
39 | "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
40 | "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
41 | "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
42 | "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
43 | "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
44 | "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
45 | "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
46 | "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
47 | "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
48 | "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
49 | "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
50 | "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
51 | "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
52 | "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
53 | "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
54 | "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
55 | "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
56 | "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
57 | "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
58 | "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
59 | "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
60 | "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
61 | "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
62 | "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
63 | "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
64 | "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
65 | "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
66 | "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
67 | "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
68 | "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
69 | "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
70 | "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
71 | "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
72 | "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
73 | "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
74 | "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
75 | "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
76 | "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
77 | "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
78 | "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
79 | "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
80 | "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
81 | "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
82 | "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
83 | "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
84 | "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
85 | "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
86 | "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
87 | "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
88 | "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
89 | "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
90 | "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
91 | "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
92 | "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
93 | "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
94 | "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
95 | "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
96 | "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
97 | "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
98 | "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
99 | "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
100 | "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
101 | "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
102 | "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
103 | "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
104 | "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
105 | "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
106 | "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
107 | "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
108 | "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
109 | "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
110 | "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
111 | "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
112 | "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
113 | "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
114 | "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
115 | "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
116 | "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
117 | "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
118 | "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
119 | "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
120 | "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
121 | "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
122 | "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
123 | "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
124 | "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
125 | "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
126 | "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
127 | "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
128 | "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
129 | "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
130 | "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
131 | "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
132 | "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
133 | "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
134 | "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
135 | "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
136 | "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
137 | "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
138 | "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
139 | "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
140 | "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
141 | "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
142 | "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
143 | "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
144 | "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
145 | "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
146 | "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
147 | "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
148 | "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
149 | "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
150 | "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
151 | "Zamorano"
152 | };
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/example/src/org/askerov/dynamicgrid/example/GridActivity.java:
--------------------------------------------------------------------------------
1 | package org.askerov.dynamicgrid.example;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 | import android.view.View;
7 | import android.widget.AdapterView;
8 | import android.widget.Toast;
9 | import org.askerov.dynamicgrid.DynamicGridView;
10 |
11 | import java.util.ArrayList;
12 | import java.util.Arrays;
13 |
14 | public class GridActivity extends Activity {
15 |
16 | private static final String TAG = GridActivity.class.getName();
17 |
18 | private DynamicGridView gridView;
19 |
20 | @Override
21 | public void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.activity_grid);
24 | gridView = (DynamicGridView) findViewById(R.id.dynamic_grid);
25 | gridView.setAdapter(new CheeseDynamicAdapter(this,
26 | new ArrayList(Arrays.asList(Cheeses.sCheeseStrings)),
27 | getResources().getInteger(R.integer.column_count)));
28 | // add callback to stop edit mode if needed
29 | // gridView.setOnDropListener(new DynamicGridView.OnDropListener()
30 | // {
31 | // @Override
32 | // public void onActionDrop()
33 | // {
34 | // gridView.stopEditMode();
35 | // }
36 | // });
37 | gridView.setOnDragListener(new DynamicGridView.OnDragListener() {
38 | @Override
39 | public void onDragStarted(int position) {
40 | Log.d(TAG, "drag started at position " + position);
41 | }
42 |
43 | @Override
44 | public void onDragPositionsChanged(int oldPosition, int newPosition) {
45 | Log.d(TAG, String.format("drag item position changed from %d to %d", oldPosition, newPosition));
46 | }
47 | });
48 | gridView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
49 | @Override
50 | public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
51 | gridView.startEditMode(position);
52 | return true;
53 | }
54 | });
55 |
56 | gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
57 | @Override
58 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
59 | Toast.makeText(GridActivity.this, parent.getAdapter().getItem(position).toString(),
60 | Toast.LENGTH_SHORT).show();
61 | }
62 | });
63 | }
64 |
65 | @Override
66 | public void onBackPressed() {
67 | if (gridView.isEditMode()) {
68 | gridView.stopEditMode();
69 | } else {
70 | super.onBackPressed();
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/askerov/DynamicGrid/f81c02853580a3820293fab4812cd441ca27ed25/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'dynamicgrid', 'example'
--------------------------------------------------------------------------------