├── settings.gradle
├── art
└── art.gif
├── handygridview
├── gradle.properties
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── huxq17
│ │ │ └── handygridview
│ │ │ ├── scrollrunner
│ │ │ ├── ICarrier.java
│ │ │ ├── OnItemMovedListener.java
│ │ │ ├── OnceRunnable.java
│ │ │ └── ScrollRunner.java
│ │ │ ├── listener
│ │ │ ├── IDrawer.java
│ │ │ └── OnItemCapturedListener.java
│ │ │ ├── utils
│ │ │ ├── SdkVerUtils.java
│ │ │ ├── ReflectUtil.java
│ │ │ └── Pools.java
│ │ │ ├── Children.java
│ │ │ ├── Child.java
│ │ │ └── HandyGridView.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── huxq17
│ │ └── handygridview
│ │ └── ExampleInstrumentedTest.java
├── build.gradle
├── .gitignore
├── proguard-rules.pro
└── gradle-jcenter-push.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── app
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── ids.xml
│ │ │ ├── styles.xml
│ │ │ ├── dimens.xml
│ │ │ └── strings.xml
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ ├── ic_delete.png
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── menu
│ │ │ └── menu_main.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ ├── drawable
│ │ │ └── s_grid_item.xml
│ │ └── layout
│ │ │ └── activity_main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── handygridview
│ │ └── example
│ │ ├── DensityUtil.java
│ │ ├── widget
│ │ ├── CustomLinearLayout.java
│ │ └── TagView.java
│ │ ├── GridViewAdapter.java
│ │ └── MainActivity.java
├── proguard-rules.pro
├── build.gradle
└── .gitignore
├── .gitattributes
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
├── gradlew
└── LICENSE
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':handygridview'
2 |
--------------------------------------------------------------------------------
/art/art.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huxq17/HandyGridView/HEAD/art/art.gif
--------------------------------------------------------------------------------
/handygridview/gradle.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huxq17/HandyGridView/HEAD/handygridview/gradle.properties
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huxq17/HandyGridView/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huxq17/HandyGridView/HEAD/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huxq17/HandyGridView/HEAD/app/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huxq17/HandyGridView/HEAD/app/src/main/res/drawable-xhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huxq17/HandyGridView/HEAD/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huxq17/HandyGridView/HEAD/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HandyGridView
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Dec 20 18:04:23 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/handygridview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/scrollrunner/ICarrier.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview.scrollrunner;
2 |
3 |
4 | import android.content.Context;
5 |
6 | public interface ICarrier {
7 | Context getContext();
8 |
9 | void onMove(int lastX, int lastY, int curX, int curY);
10 |
11 | void onDone();
12 |
13 | boolean post(Runnable runnable);
14 |
15 | boolean removeCallbacks(Runnable action);
16 | }
17 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/listener/IDrawer.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview.listener;
2 |
3 | import android.graphics.Canvas;
4 |
5 |
6 | public interface IDrawer {
7 | /**
8 | * You can draw something in gridview by this method.
9 | *
10 | * @param canvas
11 | * @param width the gridview's width
12 | * @param height the gridview's height
13 | */
14 | void onDraw(Canvas canvas, int width, int height);
15 | }
16 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/listener/OnItemCapturedListener.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview.listener;
2 |
3 | import android.view.View;
4 |
5 | public interface OnItemCapturedListener {
6 | /**
7 | * Called when user selected a view to drag.
8 | *
9 | * @param v
10 | */
11 | void onItemCaptured(View v,int position);
12 |
13 | /**
14 | * Called when user released the drag view.
15 | *
16 | * @param v
17 | */
18 | void onItemReleased(View v,int position);
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/s_grid_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 | -
10 |
11 |
12 | -
13 |
14 |
15 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/utils/SdkVerUtils.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview.utils;
2 |
3 | import android.os.Build;
4 |
5 | /**
6 | * Created by Administrator on 2017/11/26.
7 | */
8 |
9 | public class SdkVerUtils {
10 | public static boolean isAboveVersion(int version) {
11 | int sdkVersion = Build.VERSION.SDK_INT;
12 | if (sdkVersion >= version) {
13 | return true;
14 | }
15 | return false;
16 | }
17 |
18 | public static boolean isAbove19() {
19 | return isAboveVersion(Build.VERSION_CODES.KITKAT);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/scrollrunner/OnItemMovedListener.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview.scrollrunner;
2 |
3 | public interface OnItemMovedListener {
4 | /**
5 | * Called when user moved the item of gridview.
6 | * you should swipe data in this method.
7 | * @param from item's original position
8 | * @param to item's destination poisition
9 | */
10 | void onItemMoved(int from, int to);
11 |
12 | /**
13 | * return true if the item of special position can not move.
14 | * @param position
15 | * @return
16 | */
17 | boolean isFixed(int position);
18 | }
19 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\xiaolin\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 |
6 | defaultConfig {
7 | applicationId "com.handygridview.example"
8 | minSdkVersion 14
9 | targetSdkVersion 28
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | api fileTree(include: ['*.jar'], dir: 'libs')
23 | api 'com.android.support:appcompat-v7:28.0.0'
24 | // compile 'com.android.support.constraint:constraint-layout:1.0.2'
25 | api project(':handygridview')
26 | // api 'com.huxq17.handygridview:handygridview:1.1.0'
27 | }
28 |
--------------------------------------------------------------------------------
/handygridview/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | android {
3 | compileSdkVersion 28
4 |
5 | defaultConfig {
6 | minSdkVersion 11
7 | targetSdkVersion 26
8 | versionCode 1
9 | versionName "1.0"
10 |
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 |
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | //for upload jar
23 | buildscript {
24 | repositories {
25 | jcenter()
26 | }
27 | dependencies {
28 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
29 | }
30 | }
31 | apply from: 'gradle-jcenter-push.gradle'
32 |
33 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/scrollrunner/OnceRunnable.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview.scrollrunner;
2 |
3 | import android.view.View;
4 |
5 | public abstract class OnceRunnable implements Runnable {
6 | private boolean mScheduled;
7 |
8 | public final void run() {
9 | onRun();
10 | mScheduled = false;
11 | }
12 |
13 | public abstract void onRun();
14 |
15 | public void postSelf(View carrier) {
16 | postDelaySelf(carrier, 0);
17 | }
18 |
19 | public void postDelaySelf(View carrier, int delay) {
20 | if (!mScheduled) {
21 | carrier.postDelayed(this, delay);
22 | mScheduled = true;
23 | }
24 | }
25 |
26 | public void removeSelf(View carrier) {
27 | mScheduled = false;
28 | carrier.removeCallbacks(this);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/
38 | # Keystore files
39 | *.jks
40 |
41 | # External native build folder generated in Android Studio 2.2 and later
42 | .externalNativeBuild
43 |
44 | # Google Services (e.g. APIs or Firebase)
45 | google-services.json
46 |
47 | # Freeline
48 | freeline.py
49 | freeline/
50 | freeline_project_description.json
51 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/
38 | # Keystore files
39 | *.jks
40 |
41 | # External native build folder generated in Android Studio 2.2 and later
42 | .externalNativeBuild
43 |
44 | # Google Services (e.g. APIs or Firebase)
45 | google-services.json
46 |
47 | # Freeline
48 | freeline.py
49 | freeline/
50 | freeline_project_description.json
51 |
--------------------------------------------------------------------------------
/handygridview/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/
38 | # Keystore files
39 | *.jks
40 |
41 | # External native build folder generated in Android Studio 2.2 and later
42 | .externalNativeBuild
43 |
44 | # Google Services (e.g. APIs or Firebase)
45 | google-services.json
46 |
47 | # Freeline
48 | freeline.py
49 | freeline/
50 | freeline_project_description.json
51 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/handygridview/src/androidTest/java/com/huxq17/handygridview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.HandyGridView;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.huxq17.moveongridview", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/handygridview/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/utils/ReflectUtil.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview.utils;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.lang.reflect.Method;
6 |
7 | public class ReflectUtil {
8 | public static Object invokeMethod(Object targetObject, String methodName, Object[] params, Class[] paramTypes) {
9 | Object returnObj = null;
10 | if (targetObject == null || TextUtils.isEmpty(methodName)) {
11 | return null;
12 | }
13 | Method method = null;
14 | for (Class cls = targetObject.getClass(); cls != Object.class; cls = cls.getSuperclass()) {
15 | try {
16 | method = cls.getDeclaredMethod(methodName, paramTypes);
17 | break;
18 | } catch (Exception e) {
19 | // e.printStackTrace();
20 | // return null;
21 | }
22 | }
23 | if (method != null) {
24 | method.setAccessible(true);
25 | try {
26 | returnObj = method.invoke(targetObject, params);
27 | } catch (Exception e) {
28 | e.printStackTrace();
29 | }
30 | }
31 | return returnObj;
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/handygridview/example/DensityUtil.java:
--------------------------------------------------------------------------------
1 | package com.handygridview.example;
2 |
3 | import android.content.Context;
4 |
5 | public class DensityUtil {
6 |
7 | /**
8 | * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
9 | */
10 | public static int dip2px(Context context, float dpValue) {
11 | final float scale = getScale(context);
12 | return (int) (dpValue * scale + 0.5f);
13 | }
14 |
15 | /**
16 | * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
17 | */
18 | public static int px2dip(Context context, float pxValue) {
19 | final float scale = getScale(context);
20 | return (int) (pxValue / scale + 0.5f);
21 | }
22 |
23 | public static int px2sp(Context context, float pxValue) {
24 | final float fontScale = getScale(context);
25 | return (int) (pxValue / fontScale + 0.5f);
26 | }
27 |
28 | public static int sp2px(Context context, float spValue) {
29 | final float fontScale = getScale(context);
30 | return (int) (spValue * fontScale + 0.5f);
31 | }
32 |
33 | private static float getScale(Context context) {
34 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
35 | return findScale(fontScale);
36 | }
37 | private static float findScale(float scale){
38 | if(scale<=1){
39 | scale=1;
40 | }else if(scale<=1.5){
41 | scale=1.5f;
42 | }else if(scale<=2){
43 | scale=2f;
44 | }else if(scale<=3){
45 | scale=3f;
46 | }
47 | return scale;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/Children.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview;
2 |
3 | import android.view.View;
4 |
5 | import java.util.Iterator;
6 | import java.util.LinkedHashMap;
7 | import java.util.LinkedList;
8 |
9 | public class Children {
10 | private LinkedHashMap container = new LinkedHashMap<>();
11 | private LinkedList mChildren = new LinkedList<>();
12 | private HandyGridView parent;
13 |
14 | public Children(HandyGridView parent) {
15 | this.parent = parent;
16 | }
17 |
18 | public void add(int index, View view) {
19 | Child child = container.get(view);
20 | if (child == null) {
21 | child = new Child(view);
22 | child.setParent(parent);
23 | container.put(view, child);
24 | }
25 | mChildren.add(index, child);
26 | }
27 |
28 | public boolean remove(Child child) {
29 | return mChildren.remove(child);
30 | }
31 |
32 | public void remove(int index) {
33 | mChildren.remove(index);
34 | }
35 |
36 | public Child get(int index) {
37 | return mChildren.get(index);
38 | }
39 |
40 | public int indexOf(View v) {
41 | Child child = container.get(v);
42 | if (child == null) {
43 | return -2;
44 | }
45 | return mChildren.indexOf(child);
46 | }
47 |
48 | public int size() {
49 | return mChildren.size();
50 | }
51 |
52 | public void clear() {
53 | container.clear();
54 | Iterator it = mChildren.iterator();
55 | //子view从gridView移除时取消动画效果
56 | while (it.hasNext()) {
57 | Child child = it.next();
58 | child.cancel();
59 | it.remove();
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/handygridview/example/widget/CustomLinearLayout.java:
--------------------------------------------------------------------------------
1 | package com.handygridview.example.widget;
2 |
3 |
4 | import android.content.Context;
5 | import android.support.annotation.Nullable;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 | import android.widget.LinearLayout;
9 |
10 | import com.huxq17.handygridview.HandyGridView;
11 |
12 |
13 | public class CustomLinearLayout extends LinearLayout {
14 | public CustomLinearLayout(Context context) {
15 | super(context);
16 | }
17 |
18 | public CustomLinearLayout(Context context, @Nullable AttributeSet attrs) {
19 | super(context, attrs);
20 | }
21 |
22 | public CustomLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
23 | super(context, attrs, defStyleAttr);
24 | }
25 |
26 | private int mGridViewIndex = -1;
27 |
28 | @Override
29 | protected void onAttachedToWindow() {
30 | super.onAttachedToWindow();
31 | int count = getChildCount();
32 | for (int i = 0; i < count; i++) {
33 | View child = getChildAt(i);
34 | if (child instanceof HandyGridView) {
35 | HandyGridView HandyGridView = (HandyGridView) child;
36 | if (HandyGridView.isLongPressMode() || HandyGridView.isTouchMode()) {
37 | setChildrenDrawingOrderEnabled(true);
38 | mGridViewIndex = i;
39 | }
40 | return;
41 | }
42 | }
43 | }
44 |
45 | @Override
46 | protected int getChildDrawingOrder(int childCount, int i) {
47 | int index = i;
48 | if (mGridViewIndex != -1) {
49 | if (i == mGridViewIndex) {
50 | index = childCount - 1;
51 | } else if (i == childCount - 1) {
52 | index = mGridViewIndex;
53 | }
54 | }
55 | return index;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/scrollrunner/ScrollRunner.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview.scrollrunner;
2 |
3 |
4 | import android.view.animation.Interpolator;
5 | import android.view.animation.LinearInterpolator;
6 | import android.widget.Scroller;
7 |
8 | public class ScrollRunner implements Runnable {
9 | private Scroller mScroller;
10 | private ICarrier mCarrier;
11 | private int mDuration = 250;
12 | private int lastX, lastY;
13 |
14 | public ScrollRunner(ICarrier carrier) {
15 | this(carrier, new LinearInterpolator());
16 | }
17 |
18 | public ScrollRunner(ICarrier carrier, Interpolator interpolator) {
19 | mCarrier = carrier;
20 | mScroller = new Scroller(carrier.getContext(), interpolator);
21 | }
22 |
23 | public void setCarrier(ICarrier carrier) {
24 | mCarrier = carrier;
25 | }
26 |
27 | public void start(int dx, int dy) {
28 | start(dx, dy, mDuration);
29 | }
30 |
31 | public void start(int dx, int dy, int duration) {
32 | start(0, 0, dx, dy, duration);
33 | }
34 |
35 | public void start(int startX, int startY, int dx, int dy) {
36 | start(startX, startY, dx, dy, mDuration);
37 | }
38 |
39 | public void start(int startX, int startY, int dx, int dy, int duration) {
40 | this.mDuration = duration;
41 | mScroller.startScroll(startX, startY, dx, dy, duration);
42 | mCarrier.removeCallbacks(this);
43 | mCarrier.post(this);
44 | lastX = startX;
45 | lastY = startY;
46 | }
47 |
48 | public void cancel() {
49 | if (!mScroller.isFinished()) {
50 | mCarrier.removeCallbacks(this);
51 | mScroller.forceFinished(true);
52 | }
53 | }
54 |
55 | public int getCurX() {
56 | return mScroller.getCurrX();
57 | }
58 |
59 | public int getCurY() {
60 | return mScroller.getCurrY();
61 | }
62 |
63 | public void abortAnimation() {
64 | if (!mScroller.isFinished()) {
65 | mScroller.abortAnimation();
66 | }
67 | }
68 |
69 | public boolean isRunning() {
70 | return !mScroller.isFinished();
71 | }
72 |
73 | @Override
74 | public void run() {
75 | if (mScroller.computeScrollOffset()) {
76 | int currentX = mScroller.getCurrX();
77 | int currentY = mScroller.getCurrY();
78 | mCarrier.onMove(lastX, lastY, currentX, currentY);
79 | lastX = currentX;
80 | lastY = currentY;
81 | if (currentX == mScroller.getFinalX() && currentY == mScroller.getFinalY()) {
82 | mCarrier.removeCallbacks(this);
83 | mCarrier.onDone();
84 | } else {
85 | mCarrier.post(this);
86 | }
87 | } else {
88 | mCarrier.removeCallbacks(this);
89 | mCarrier.onDone();
90 | }
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
27 |
28 |
31 |
32 |
40 |
41 |
44 |
45 |
52 |
53 |
56 |
57 |
60 |
61 |
69 |
70 |
73 |
74 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/Child.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 |
6 | import com.huxq17.handygridview.scrollrunner.ICarrier;
7 | import com.huxq17.handygridview.scrollrunner.ScrollRunner;
8 |
9 |
10 | public class Child implements ICarrier {
11 | public int position;
12 | public View view;
13 | private ScrollRunner mRunner;
14 | private int from, to;
15 | private boolean hasNext = false;
16 | private HandyGridView parent;
17 |
18 | public Child(View view) {
19 | this.view = view;
20 | mRunner = new ScrollRunner(this);
21 | }
22 |
23 | public void cancel() {
24 | mRunner.cancel();
25 | hasNext = false;
26 | }
27 |
28 | public void setParent(HandyGridView parent) {
29 | this.parent = parent;
30 | }
31 |
32 | private void move(final int offsetX, final int offsetY) {
33 | mRunner.start(offsetX, offsetY);
34 | }
35 |
36 | public void moveTo(int from, int to) {
37 | this.from = from;
38 | this.to = to;
39 | int[] start = parent.getLeftAndTopForPosition(from);
40 | int[] end = parent.getLeftAndTopForPosition(to);
41 | if (!mRunner.isRunning()) {
42 | int offsetX = end[0] - start[0];
43 | int offsetY = end[1] - start[1];
44 | move(offsetX, offsetY);
45 | } else {
46 | hasNext = true;
47 | }
48 | }
49 |
50 | @Override
51 | public void onDone() {
52 | int[] start = new int[]{view.getLeft(), view.getTop()};
53 | from = parent.pointToPosition(start[0], start[1]);
54 |
55 | int[] end = parent.getLeftAndTopForPosition(to);
56 | if (hasNext) {
57 | if (from != to) {
58 | int offsetX = end[0] - start[0];
59 | int offsetY = end[1] - start[1];
60 | move(offsetX, offsetY);
61 | }
62 | hasNext = false;
63 | }
64 | }
65 |
66 | @Override
67 | public void onMove(int lastX, int lastY, int curX, int curY) {
68 | int deltaX = curX - lastX;
69 | int deltaY = curY - lastY;
70 | view.offsetLeftAndRight(deltaX);
71 | view.offsetTopAndBottom(deltaY);
72 | }
73 |
74 | @Override
75 | public boolean post(Runnable runnable) {
76 | return view.post(runnable);
77 | }
78 |
79 | @Override
80 | public boolean removeCallbacks(Runnable action) {
81 | return view.removeCallbacks(action);
82 | }
83 |
84 | @Override
85 | public Context getContext() {
86 | return view.getContext();
87 | }
88 |
89 | @Override
90 | public boolean equals(Object obj) {
91 | if (obj == this) return true;
92 | if (obj instanceof Child) {
93 | Child child = (Child) obj;
94 | if (this.view == child.view) {
95 | return true;
96 | }
97 | }
98 | return super.equals(obj);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/com/handygridview/example/GridViewAdapter.java:
--------------------------------------------------------------------------------
1 | package com.handygridview.example;
2 |
3 | import android.content.Context;
4 | import android.graphics.drawable.Drawable;
5 | import android.util.Log;
6 | import android.view.Gravity;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.BaseAdapter;
10 | import android.widget.GridView;
11 |
12 | import com.handygridview.example.widget.TagView;
13 | import com.huxq17.handygridview.scrollrunner.OnItemMovedListener;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | public class GridViewAdapter extends BaseAdapter implements OnItemMovedListener, TagView.OnTagDeleteListener {
19 | private Context context;
20 | private List mDatas = new ArrayList<>();
21 |
22 | public GridViewAdapter(Context context, List dataList) {
23 | this.context = context;
24 | this.mDatas.addAll(dataList);
25 | }
26 |
27 | private GridView mGridView;
28 | private boolean inEditMode = false;
29 |
30 | public void setData(List dataList) {
31 | this.mDatas.clear();
32 | this.mDatas.addAll(dataList);
33 | notifyDataSetChanged();
34 | }
35 |
36 | public void setInEditMode(boolean inEditMode) {
37 | this.inEditMode = inEditMode;
38 | notifyDataSetChanged();
39 | }
40 |
41 | @Override
42 | public int getCount() {
43 | return mDatas.size();
44 | }
45 |
46 | @Override
47 | public String getItem(int position) {
48 | return mDatas.get(position);
49 | }
50 |
51 | @Override
52 | public long getItemId(int position) {
53 | return position;
54 | }
55 |
56 | @Override
57 | public View getView(int position, View convertView, ViewGroup parent) {
58 | if (mGridView == null) {
59 | mGridView = (GridView) parent;
60 | }
61 | TagView textView;
62 | if (convertView == null) {
63 | textView = new TagView(context);
64 | convertView = textView;
65 | textView.setMaxLines(1);
66 | textView.setHeight(DensityUtil.dip2px(context, 40));
67 | int id = context.getResources().getIdentifier("s_grid_item", "drawable", context.getPackageName());
68 | Drawable drawable = context.getResources().getDrawable(id);
69 | textView.setBackgroundDrawable(drawable);
70 | textView.setGravity(Gravity.CENTER);
71 | } else {
72 | textView = (TagView) convertView;
73 | }
74 | if (!isFixed(position)) {
75 | textView.showDeleteIcon(inEditMode);
76 | } else {
77 | textView.showDeleteIcon(false);
78 | }
79 | textView.setText(getItem(position));
80 | textView.setOnTagDeleteListener(this);
81 | return convertView;
82 | }
83 |
84 | @Override
85 | public void onItemMoved(int from, int to) {
86 | String s = mDatas.remove(from);
87 | mDatas.add(to, s);
88 | }
89 |
90 | @Override
91 | public boolean isFixed(int position) {
92 | //When postion==0,the item can not be dragged.
93 | if (position == 0) {
94 | return true;
95 | }
96 | return false;
97 | }
98 |
99 | @Override
100 | public void onDelete(View deleteView) {
101 | int index = mGridView.indexOfChild(deleteView);
102 | if (index <= 0) return;
103 | int position = index + mGridView.getFirstVisiblePosition();
104 | mDatas.remove(position);
105 | notifyDataSetChanged();
106 | }
107 | }
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/utils/Pools.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview.utils;
2 |
3 | public final class Pools {
4 |
5 | /**
6 | * Interface for managing a pool of objects.
7 | *
8 | * @param The pooled type.
9 | */
10 | public static interface Pool {
11 |
12 | /**
13 | * @return An instance from the pool if such, null otherwise.
14 | */
15 | public T acquire();
16 |
17 | /**
18 | * Release an instance to the pool.
19 | *
20 | * @param instance The instance to release.
21 | * @return Whether the instance was put in the pool.
22 | *
23 | * @throws IllegalStateException If the instance is already in the pool.
24 | */
25 | public boolean release(T instance);
26 | }
27 |
28 | private Pools() {
29 | /* do nothing - hiding constructor */
30 | }
31 |
32 | /**
33 | * Simple (non-synchronized) pool of objects.
34 | *
35 | * @param The pooled type.
36 | */
37 | public static class SimplePool implements Pool {
38 | private final Object[] mPool;
39 |
40 | private int mPoolSize;
41 |
42 | /**
43 | * Creates a new instance.
44 | *
45 | * @param maxPoolSize The max pool size.
46 | *
47 | * @throws IllegalArgumentException If the max pool size is less than zero.
48 | */
49 | public SimplePool(int maxPoolSize) {
50 | if (maxPoolSize <= 0) {
51 | throw new IllegalArgumentException("The max pool size must be > 0");
52 | }
53 | mPool = new Object[maxPoolSize];
54 | }
55 |
56 | @Override
57 | @SuppressWarnings("unchecked")
58 | public T acquire() {
59 | if (mPoolSize > 0) {
60 | final int lastPooledIndex = mPoolSize - 1;
61 | T instance = (T) mPool[lastPooledIndex];
62 | mPool[lastPooledIndex] = null;
63 | mPoolSize--;
64 | return instance;
65 | }
66 | return null;
67 | }
68 |
69 | @Override
70 | public boolean release(T instance) {
71 | if (isInPool(instance)) {
72 | throw new IllegalStateException("Already in the pool!");
73 | }
74 | if (mPoolSize < mPool.length) {
75 | mPool[mPoolSize] = instance;
76 | mPoolSize++;
77 | return true;
78 | }
79 | return false;
80 | }
81 |
82 | private boolean isInPool(T instance) {
83 | for (int i = 0; i < mPoolSize; i++) {
84 | if (mPool[i] == instance) {
85 | return true;
86 | }
87 | }
88 | return false;
89 | }
90 | }
91 |
92 | /**
93 | * Synchronized) pool of objects.
94 | *
95 | * @param The pooled type.
96 | */
97 | public static class SynchronizedPool extends SimplePool {
98 | private final Object mLock = new Object();
99 |
100 | /**
101 | * Creates a new instance.
102 | *
103 | * @param maxPoolSize The max pool size.
104 | *
105 | * @throws IllegalArgumentException If the max pool size is less than zero.
106 | */
107 | public SynchronizedPool(int maxPoolSize) {
108 | super(maxPoolSize);
109 | }
110 |
111 | @Override
112 | public T acquire() {
113 | synchronized (mLock) {
114 | return super.acquire();
115 | }
116 | }
117 |
118 | @Override
119 | public boolean release(T element) {
120 | synchronized (mLock) {
121 | return super.release(element);
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/com/handygridview/example/widget/TagView.java:
--------------------------------------------------------------------------------
1 | package com.handygridview.example.widget;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Rect;
6 | import android.graphics.drawable.Drawable;
7 | import android.util.Log;
8 | import android.view.MotionEvent;
9 | import android.view.View;
10 |
11 | import com.handygridview.example.DensityUtil;
12 | import com.handygridview.example.R;
13 |
14 |
15 | public class TagView extends android.support.v7.widget.AppCompatTextView {
16 | private Drawable deleteIcon;
17 | private int iconWidth;
18 | private int iconHeight;
19 | private boolean showIcon = true;
20 | private Rect mDelteRect;
21 | private Rect mAssumeDelteRect;
22 |
23 | public TagView(Context context) {
24 | super(context);
25 | // int id = context.getResources().getIdentifier("ic_delete", "drawable", context.getPackageName());
26 | deleteIcon = context.getResources().getDrawable(R.drawable.ic_delete);
27 | }
28 |
29 | @Override
30 | protected void onDraw(Canvas canvas) {
31 | super.onDraw(canvas);
32 | if (mAssumeDelteRect == null) {
33 | setDeleteBounds();
34 | }
35 | if (showIcon) {
36 | deleteIcon.draw(canvas);
37 | }
38 | }
39 |
40 | private void setDeleteBounds() {
41 | iconWidth = deleteIcon.getIntrinsicWidth();
42 | iconHeight = deleteIcon.getIntrinsicHeight();
43 | int left = getWidth() - iconWidth;
44 | int top = 0;
45 | mDelteRect = new Rect(left, top, left + iconWidth, top + iconHeight);
46 | //padding扩大了icon的点击范围
47 | int padding = DensityUtil.dip2px(getContext(), 10);
48 | mAssumeDelteRect = new Rect(mDelteRect.left, mDelteRect.top, mDelteRect.left + iconWidth + padding, mDelteRect.top + iconHeight + padding);
49 | deleteIcon.setBounds(mDelteRect);
50 | }
51 |
52 | @Override
53 | public boolean onTouchEvent(MotionEvent event) {
54 | int action = event.getAction();
55 | int x = (int) event.getX();
56 | int y = (int) event.getY();
57 | boolean contains = mAssumeDelteRect.contains(x, y);
58 | switch (action) {
59 | case MotionEvent.ACTION_DOWN:
60 | if (contains && showIcon) {
61 | return true;
62 | }
63 | break;
64 | case MotionEvent.ACTION_MOVE:
65 | break;
66 | case MotionEvent.ACTION_UP:
67 | if (contains && showIcon) {
68 | if (mListener != null) {
69 | mListener.onDelete(this);
70 | }
71 | return true;
72 | }
73 | break;
74 | }
75 | return super.onTouchEvent(event);
76 | }
77 |
78 | private void log(String msg) {
79 | Log.e(getClass().getCanonicalName(), msg);
80 | }
81 |
82 | public void showDeleteIcon(boolean show) {
83 | showIcon = show;
84 | invalidate();
85 | }
86 |
87 | private OnTagDeleteListener mListener;
88 |
89 | public void setOnTagDeleteListener(OnTagDeleteListener listener) {
90 | mListener = listener;
91 | }
92 |
93 | public interface OnTagDeleteListener {
94 | /**
95 | * Delete view.
96 | *
97 | * @param deleteView
98 | */
99 | void onDelete(View deleteView);
100 | }
101 | // @Override
102 | // protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
103 | //// int widthSize = 100;
104 | // int heightSize = DensityUtil.dip2px(getContext(),40);
105 | ////
106 | //// widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
107 | // heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.AT_MOST);
108 | // setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
109 | //// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
110 | // Log.e("onmea","width="+getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)+";height="+getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
111 | // }
112 | }
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HandyGridView
2 |
3 | []( https://android-arsenal.com/details/1/6571 )
4 |
5 | HandyGridView本质上是一个GridView,所以你也可以当成普通的GridView来使用,HandyGridView继承了GridView并在此之上添加了item拖动和交换,绘制图文等功能。
6 | 由于只是一个GridView,所以性能比目前其他大部分解决方案都要好。
7 |
8 | HandyGridView is a high-performance drag and drop GridView, it extends GridView, you can drag drop GridView item to sort the labels,and draw something on the GridView. Just use the HandyGridView like a GridView.
9 |
10 | ---
11 |
12 | ### Screenshots
13 |
14 |
15 |
16 | ---
17 | ### Usage
18 |
19 | #### Gradle
20 |
21 | ```groovy
22 | dependencies {
23 | compile 'com.huxq17.handygridview:handygridview:1.2.0'
24 |
25 | }
26 | ```
27 |
28 | #### minSdkVersion 11
29 |
30 | #### HandyGridView's three modes:
31 |
32 | Mode | introduction
33 | ---|---
34 | TOUCH | Edit mode,the item can be dragged
35 | LONG_PRESS | Long press mode,item can be dragged after long press.
36 | NONE | Item can not be dragged, jsut like normal GridView.
37 |
38 | Usage:
39 |
40 | ```
41 | HandyGridView#setMode(TOUCH|LONG_PRESS|NONE);
42 |
43 | ```
44 |
45 | #### Adapter
46 |
47 | HandyGridView会在item被拖动交换时发出通知,如果想要做出对应数据上的变化,则可以在Apdater中实现OnItemMovedListener,示例如下:
48 |
49 | HandyGridView will send a notification to notify you swip the data source when its item's order is changed. the usage is as follows:
50 |
51 | ```
52 |
53 | public class GridViewAdapter extends BaseAdapter implements OnItemMovedListener{
54 | @Override
55 | public void onItemMoved(int from, int to) {
56 | String s = mDatas.remove(from);
57 | mDatas.add(to, s);
58 | }
59 |
60 | @Override
61 | public boolean isFixed(int position) {
62 | //When postion==0,the item can not be dragged.
63 | if (position == 0) {
64 | return true;
65 | }
66 | return false;
67 | }
68 | }
69 | ```
70 |
71 | #### 绘制图文
72 | HandyGridView提供了在gridview上绘制图文的接口,示例如下:
73 |
74 | You can draw something on HandyGridView, the usage is as follows:
75 |
76 | ```
77 | mGridView.setDrawer(new IDrawer() {
78 | @Override
79 | public void onDraw(Canvas canvas, int width, int height) {
80 | if (!mGridView.isNoneMode()) {
81 | int offsetX = -DensityUtil.dip2px(MainActivity.this, 10);
82 | int offsetY = -DensityUtil.dip2px(MainActivity.this, 10);
83 | //文字绘制于gridview的右下角,并向左,向上偏移10dp。
84 | //Draw text on the right-bottom of GridView.
85 | drawTips(canvas, width + offsetX, height + offsetY);
86 | }
87 | }
88 | },false);
89 |
90 | private void drawTips(Canvas canvas, int width, int height) {
91 | if (mTextPaint == null) {
92 | mTextPaint = new TextPaint();
93 | mTextPaint.setColor(Color.parseColor("#CFCFCF"));
94 | mTextPaint.setTextSize(DensityUtil.dip2px(MainActivity.this, 12));
95 | Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
96 | textHeight = (int) (fontMetrics.bottom - fontMetrics.top) + 1;
97 | textWidth = (int) mTextPaint.measureText(paintText) + 1;
98 | }
99 | width = width - textWidth;
100 | height = height - textHeight;
101 | if (tipsLayout == null) {
102 | tipsLayout = new StaticLayout(paintText, mTextPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.5f, 0f, false);
103 | }
104 | canvas.translate(width, height);
105 | tipsLayout.draw(canvas);
106 | }
107 |
108 | ```
109 |
110 |
111 | 以上就是主要的用法了,[更多的用法可以参考example](https://github.com/huxq17/HandyGridView/blob/master/app/src/main/java/com/handygridview/example/MainActivity.java).
112 |
113 | The above is the main usage,[click to get more](https://github.com/huxq17/HandyGridView/blob/master/app/src/main/java/com/handygridview/example/MainActivity.java).
114 |
115 |
116 |
117 | ---
118 |
119 | ### 更新日志
120 | 2018-8-29:
121 | 1.解决HandyGridView在没有item并且horizontalSpacing为0dp或者没有设置时触摸会挂掉的问题
122 |
123 | 2017-12-29:
124 | 1.解决某些小米手机上item拖动交换时会闪烁的问题,更新到1.1.0
125 |
126 | ---
127 | ### LICENSE
128 |
129 | [Apache License 2.0](LICENSE)
130 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/handygridview/gradle-jcenter-push.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'com.jfrog.bintray'
3 |
4 |
5 | task androidJavadocs(type: Javadoc) {
6 | failOnError false
7 | source = android.sourceSets.main.java.srcDirs
8 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
9 | allprojects {
10 | tasks.withType(Javadoc) {
11 | options.encoding = "UTF-8"
12 | }
13 | }
14 | }
15 |
16 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
17 | classifier = 'javadoc'
18 | from androidJavadocs.destinationDir
19 | }
20 |
21 | task androidSourcesJar(type: Jar) {
22 | classifier = 'sources'
23 | from android.sourceSets.main.java.sourceFiles
24 | }
25 |
26 | artifacts {
27 | archives androidJavadocsJar
28 | archives androidSourcesJar
29 | }
30 |
31 | publishing {
32 | publications {
33 | mavenJava(MavenPublication) {
34 | groupId GROUP
35 | version VERSION_NAME
36 | artifactId POM_ARTIFACT_ID
37 | artifact "${project.buildDir}/outputs/aar/${project.name}-release.aar"
38 | artifact androidJavadocsJar
39 | artifact androidSourcesJar
40 |
41 | pom.withXml {
42 | Node root = asNode()
43 | root.appendNode('name', POM_ARTIFACT_ID)
44 | root.appendNode('description', POM_DESCRIPTION)
45 | root.appendNode('url', POM_URL)
46 |
47 | def issues = root.appendNode('issueManagement')
48 | issues.appendNode('system', 'github')
49 | issues.appendNode('url', ISSUE_URL)
50 |
51 | def scm = root.appendNode('scm')
52 | scm.appendNode('url', POM_SCM_URL)
53 | scm.appendNode('connection', POM_SCM_CONNECTION)
54 | scm.appendNode('developerConnection', POM_SCM_DEV_CONNECTION)
55 |
56 | def license = root.appendNode('licenses').appendNode('license')
57 | license.appendNode('name', POM_LICENCE_NAME)
58 | license.appendNode('url', POM_LICENCE_URL)
59 | license.appendNode('distribution', POM_LICENCE_DIST)
60 |
61 | def developer = root.appendNode('developers').appendNode('developer')
62 | developer.appendNode('id', POM_DEVELOPER_ID)
63 | developer.appendNode('name', POM_DEVELOPER_NAME)
64 | developer.appendNode('email', POM_DEVELOPER_EMAIL)
65 | developer.appendNode('url', POM_DEVELOPER_URL)
66 |
67 | // def dependenciesNode = asNode().appendNode('dependencies')
68 | //
69 | // //Iterate over the compile dependencies (we don't want the test ones), adding a node for each
70 | // configurations.compile.allDependencies.each {
71 | // def dependencyNode = dependenciesNode.appendNode('dependency')
72 | // dependencyNode.appendNode('groupId', it.group)
73 | // dependencyNode.appendNode('artifactId', it.name)
74 | // dependencyNode.appendNode('version', it.version)
75 | // }
76 | }
77 | }
78 | }
79 | }
80 |
81 | def getBintrayUserProperty(Properties properties) {
82 | return properties.get('BINTRAY_USER', "")
83 | // return hasProperty('BINTRAY_USER') ? BINTRAY_USER : ""
84 | }
85 |
86 | def getBintrayApiKeyProperty(Properties properties) {
87 | return properties.get('BINTRAY_APIKEY', "")
88 | // return hasProperty('BINTRAY_APIKEY') ? BINTRAY_APIKEY : ""
89 | }
90 |
91 | def getGpgPassphraseProperty() {
92 | return hasProperty('GPG_PASSPHRASE') ? GPG_PASSPHRASE : ""
93 | }
94 |
95 | def getUserTokenProperty() {
96 | return hasProperty('USERTOKEN') ? USERTOKEN : ""
97 | }
98 |
99 | def getUserPasswordProperty() {
100 | return hasProperty('USERPASSWORD') ? USERPASSWORD : ""
101 | }
102 |
103 | // gradle bintrayUpload
104 | bintray {
105 | Properties properties = new Properties()
106 | File propertyFile = new File(rootDir.getAbsolutePath() + "/local.properties")
107 | properties.load(propertyFile.newDataInputStream())
108 |
109 | user = getBintrayUserProperty(properties)
110 | key = getBintrayApiKeyProperty(properties)
111 |
112 | def passphrase = getGpgPassphraseProperty()
113 | def userToken = getUserTokenProperty()
114 | def userPassword = getUserPasswordProperty()
115 | publications = ['mavenJava']
116 |
117 | dryRun = false
118 | publish = true
119 | pkg {
120 | repo = 'maven'
121 | name = POM_ARTIFACT_ID
122 | desc = POM_NAME
123 | websiteUrl = POM_URL
124 | issueTrackerUrl = ISSUE_URL
125 | vcsUrl = GIT_URL
126 | licenses = ['Apache-2.0']
127 | labels = ['android', 'aar']
128 | publicDownloadNumbers = true
129 |
130 | version {
131 | name = VERSION_NAME
132 | vcsTag = VERSION_NAME
133 | gpg {
134 | sign = true //Determines whether to GPG sign the files. The default is false
135 | passphrase = 'passphrase' //Optional. The passphrase for GPG signing'
136 | }
137 | mavenCentralSync {
138 | sync = false
139 | //Optional (true by default). Determines whether to sync the version to Maven Central.
140 | user = userToken //OSS user token
141 | password = userPassword //OSS user password
142 | close = '1'
143 | //Optional property. By default the staging repository is closed and artifacts are released to Maven Central. You can optionally turn this behaviour off (by puting 0 as value) and release the version manually.
144 | }
145 | }
146 | }
147 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/handygridview/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.handygridview.example;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.pm.PackageManager;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.os.Bundle;
10 | import android.support.v4.app.ActivityCompat;
11 | import android.text.Layout;
12 | import android.text.StaticLayout;
13 | import android.text.TextPaint;
14 | import android.util.Log;
15 | import android.view.View;
16 | import android.view.ViewGroup;
17 | import android.widget.AdapterView;
18 | import android.widget.Button;
19 | import android.widget.TextView;
20 | import android.widget.Toast;
21 |
22 | import com.huxq17.handygridview.HandyGridView;
23 | import com.huxq17.handygridview.listener.IDrawer;
24 | import com.huxq17.handygridview.listener.OnItemCapturedListener;
25 |
26 | import java.util.ArrayList;
27 | import java.util.Iterator;
28 | import java.util.List;
29 |
30 | public class MainActivity extends Activity implements View.OnClickListener {
31 | private HandyGridView mGridView;
32 | private List strList;
33 | private static final int REQUEST_EXTERNAL_STORAGE = 1;
34 | private static String[] PERMISSIONS_STORAGE = {
35 | Manifest.permission.READ_EXTERNAL_STORAGE,
36 | Manifest.permission.WRITE_EXTERNAL_STORAGE
37 | };
38 |
39 | private TextView enableSelectorTv, changeModeTv, addTagTv;
40 | private Button recoveryTagTv;
41 | private GridViewAdapter adapter;
42 | private ViewGroup outLayout;
43 |
44 | @Override
45 | protected void onCreate(Bundle savedInstanceState) {
46 | super.onCreate(savedInstanceState);
47 | setContentView(R.layout.activity_main);
48 | initData();
49 | initView();
50 | int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
51 | if (permission != PackageManager.PERMISSION_GRANTED) {
52 | ActivityCompat.requestPermissions(
53 | this,
54 | PERMISSIONS_STORAGE,
55 | REQUEST_EXTERNAL_STORAGE
56 | );
57 | }
58 | }
59 |
60 | private void initView() {
61 | enableSelectorTv = findViewById(R.id.text_enable_selector);
62 | changeModeTv = findViewById(R.id.text_change_mode);
63 | addTagTv = findViewById(R.id.text_add_tag);
64 | recoveryTagTv = findViewById(R.id.text_recovery_tag);
65 | outLayout = findViewById(R.id.out_layout);
66 |
67 | mGridView = findViewById(R.id.grid_tips);
68 | adapter = new GridViewAdapter(this, strList);
69 | mGridView.setAdapter(adapter);
70 |
71 | setMode(HandyGridView.MODE.LONG_PRESS);
72 | mGridView.setAutoOptimize(false);
73 | //当gridview可以滚动并且被拖动的item位于gridview的顶部或者底部时,设置gridview滚屏的速度,
74 | // 每秒移动的像素点个数,默认750,可不设置。
75 | mGridView.setScrollSpeed(750);
76 | // adapter.notifyDataSetChanged();
77 | mGridView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
78 | @Override
79 | public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
80 | if (!mGridView.isTouchMode()&&!mGridView.isNoneMode() && !adapter.isFixed(position)) {//long press enter edit mode.
81 | setMode(HandyGridView.MODE.TOUCH);
82 | return true;
83 | }
84 | return false;
85 | }
86 | });
87 | mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
88 | @Override
89 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
90 | Toast.makeText(MainActivity.this, "click item at " + position, Toast.LENGTH_SHORT).show();
91 | }
92 | });
93 | mGridView.setOnItemCapturedListener(new OnItemCapturedListener() {
94 | @Override
95 | public void onItemCaptured(View v, int position) {
96 | v.setScaleX(1.2f);
97 | v.setScaleY(1.2f);
98 | }
99 |
100 | @Override
101 | public void onItemReleased(View v, int position) {
102 | v.setScaleX(1f);
103 | v.setScaleY(1f);
104 | }
105 |
106 | });
107 | mGridView.setDrawer(new IDrawer() {
108 | @Override
109 | public void onDraw(Canvas canvas, int width, int height) {
110 | if (!mGridView.isNoneMode()) {
111 | int offsetX = -DensityUtil.dip2px(MainActivity.this, 10);
112 | int offsetY = -DensityUtil.dip2px(MainActivity.this, 10);
113 | //文字绘制于gridview的右下角,并向左,向上偏移10dp。
114 | drawTips(canvas, width + offsetX, height + offsetY);
115 | }
116 | }
117 | },false);
118 | enableSelectorTv.setOnClickListener(this);
119 | // enableSelectorTv.performClick();
120 | changeModeTv.setOnClickListener(this);
121 | addTagTv.setOnClickListener(this);
122 | recoveryTagTv.setOnClickListener(this);
123 | }
124 |
125 | String paintText = "长按排序或删除";
126 | int textWidth;
127 | int textHeight;
128 | StaticLayout tipsLayout;
129 | private TextPaint mTextPaint;
130 |
131 | private void drawTips(Canvas canvas, int width, int height) {
132 | if (mTextPaint == null) {
133 | mTextPaint = new TextPaint();
134 | mTextPaint.setColor(Color.parseColor("#cccccc"));
135 | mTextPaint.setTextSize(DensityUtil.dip2px(MainActivity.this, 12));
136 | Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
137 | textHeight = (int) (fontMetrics.bottom - fontMetrics.top) + 1;
138 | textWidth = (int) mTextPaint.measureText(paintText) + 1;
139 | }
140 | width = width - textWidth;
141 | height = height - textHeight;
142 | if (tipsLayout == null) {
143 | tipsLayout = new StaticLayout(paintText, mTextPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.5f, 0f, false);
144 | }
145 | canvas.translate(width, height);
146 | tipsLayout.draw(canvas);
147 | }
148 |
149 | @Override
150 | public void onClick(View v) {
151 | int id = v.getId();
152 | switch (id) {
153 | case R.id.text_enable_selector:
154 | boolean isSelectorEnabled = mGridView.isSelectorEnabled();
155 | //建议传入false,不要开启selector,不然item移动以后,在item原来的位置会有残影逐渐消失的效果。
156 | //默认是false。
157 | mGridView.setSelectorEnabled(!isSelectorEnabled);
158 | enableSelectorTv.setText(!isSelectorEnabled ? "selector已开启" : "selector已关闭");
159 | break;
160 | case R.id.text_change_mode:
161 | HandyGridView.MODE curMode = mGridView.getMode();
162 | int index = HandyGridView.MODE.indexOf(curMode);
163 | index = (index + 1) % HandyGridView.MODE.values().length;
164 | //有三种模式分别是点击拖动,长按拖动和不拖动,分别对应的是TOUCH,LONG_PRESS和NONE
165 | setMode(HandyGridView.MODE.get(index));
166 | break;
167 | case R.id.text_add_tag:
168 | addTag();
169 | break;
170 | case R.id.text_recovery_tag:
171 | // moveTaskToBack(true);
172 | recoveryTag();
173 | break;
174 | }
175 | }
176 |
177 | private void recoveryTag() {
178 | for (Iterator iter = strList.iterator(); iter.hasNext(); ) {
179 | String str = iter.next();
180 | if (str.contains("更多")) {
181 | iter.remove();
182 | }
183 | }
184 | setMode(HandyGridView.MODE.LONG_PRESS);
185 | adapter.setData(strList);
186 | outLayout.setClipChildren(false);
187 | }
188 |
189 | private void addTag() {
190 | for (int i = 0; i < 4; i++) {
191 | strList.add("更多 "+(strList.size()-1));
192 | }
193 | adapter.setData(strList);
194 | outLayout.setClipChildren(true);
195 | mGridView.smoothScrollToPosition(adapter.getCount() - 1);
196 | }
197 |
198 | private void setMode(HandyGridView.MODE mode) {
199 | mGridView.setMode(mode);
200 | changeModeTv.setText(mode.toString());
201 | adapter.setInEditMode(mode == HandyGridView.MODE.TOUCH);
202 | }
203 |
204 | private void log(String msg) {
205 | Log.e(getClass().getCanonicalName(), msg);
206 | }
207 |
208 | public void initData() {
209 | strList = new ArrayList<>();
210 | // for (int i = 0; i < 10; i++) {
211 | // strList.add("ITEM " + i);
212 | // }
213 | strList.add("头条");
214 | strList.add("轻松一刻");
215 | strList.add("视频");
216 | strList.add("娱乐");
217 | strList.add("段子");
218 | strList.add("跟帖");
219 | strList.add("活力学院");
220 | strList.add("北京");
221 | strList.add("社会");
222 | strList.add("军事");
223 | strList.add("热点");
224 | strList.add("直播");
225 | strList.add("网易号");
226 | strList.add("房产");
227 | }
228 |
229 | }
230 |
--------------------------------------------------------------------------------
/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,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/handygridview/src/main/java/com/huxq17/handygridview/HandyGridView.java:
--------------------------------------------------------------------------------
1 | package com.huxq17.handygridview;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Rect;
7 | import android.graphics.drawable.ColorDrawable;
8 | import android.graphics.drawable.Drawable;
9 | import android.os.Build;
10 | import android.util.AttributeSet;
11 | import android.util.Log;
12 | import android.view.MotionEvent;
13 | import android.view.View;
14 | import android.view.ViewConfiguration;
15 | import android.view.ViewGroup;
16 | import android.view.animation.AccelerateDecelerateInterpolator;
17 | import android.widget.AbsListView;
18 | import android.widget.AdapterView;
19 | import android.widget.GridView;
20 | import android.widget.ListAdapter;
21 |
22 | import com.huxq17.handygridview.listener.IDrawer;
23 | import com.huxq17.handygridview.listener.OnItemCapturedListener;
24 | import com.huxq17.handygridview.scrollrunner.ICarrier;
25 | import com.huxq17.handygridview.scrollrunner.OnItemMovedListener;
26 | import com.huxq17.handygridview.scrollrunner.ScrollRunner;
27 | import com.huxq17.handygridview.utils.ReflectUtil;
28 | import com.huxq17.handygridview.utils.SdkVerUtils;
29 |
30 | public class HandyGridView extends GridView implements AdapterView.OnItemLongClickListener, AdapterView.OnItemClickListener, ICarrier {
31 | private int mScrollY;
32 | private int mFirstTop, mFirstLeft;
33 | private int mFirstVisibleFirstItem = -1;
34 | private float mLastMotionX, mLastMotionY;
35 | private Children mChildren;
36 | private int mTouchSlop;
37 | private OnItemLongClickListener mOnItemLongClickListener;
38 | private OnScrollListener mOnScrollListener;
39 | private ScrollRunner mScrollRunner;
40 | private int mScrollSpeed = 750;
41 | private boolean mClipToPadding = false;
42 | private Rect mGridViewVisibleRect;
43 | private MotionEvent mCurrMotionEvent;
44 |
45 | private ListAdapter mAdapter;
46 | private OnItemMovedListener mItemMovedListener;
47 |
48 | private View mDraggedView;
49 | private int mDraggedIndex = -1;
50 | private Rect mDraggedRect = new Rect();
51 | private int mColumnWidth, mRowHeight, mColumnsNum;
52 | private int mHorizontalSpacing;
53 | private int mVerticalSpacing;
54 | private int mDraggedPosition = INVALID_POSITION;
55 | private OnItemClickListener mOnItemClickListener;
56 | private boolean mShouldMove = false;
57 | private boolean mUseSelector = false;
58 | private boolean mAutoOptimize = true;
59 | private MODE mode = MODE.NONE;
60 |
61 | private Drawable mSelector;
62 | private Drawable mSpaceDrawable;
63 | private IDrawer mDrawer;
64 | private boolean mDrawOnTop = false;
65 |
66 | public HandyGridView(Context context) {
67 | this(context, null);
68 | }
69 |
70 | public HandyGridView(Context context, AttributeSet attrs) {
71 | super(context, attrs);
72 | init(context);
73 | }
74 |
75 | private void init(Context context) {
76 | mScrollRunner = new ScrollRunner(this, new AccelerateDecelerateInterpolator());
77 | setChildrenDrawingOrderEnabled(true);
78 | super.setOnItemLongClickListener(this);
79 | super.setOnItemClickListener(this);
80 | setOverScrollMode(OVER_SCROLL_NEVER);
81 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
82 | mChildren = new Children(this);
83 | super.setOnScrollListener(new OnScrollListener() {
84 | @Override
85 | public void onScrollStateChanged(AbsListView view, int scrollState) {
86 | if (mOnScrollListener != null) {
87 | mOnScrollListener.onScrollStateChanged(view, scrollState);
88 | }
89 | }
90 |
91 | @Override
92 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
93 | if (visibleItemCount != 0) {
94 | mFirstVisibleFirstItem = firstVisibleItem;
95 | getBasicValues(firstVisibleItem);
96 | if (mOnScrollListener != null) {
97 | mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
98 | }
99 | }
100 | }
101 | });
102 | }
103 |
104 | private void getBasicValues(int firstVisibleItem) {
105 | View firstChild = getChildAt(0);
106 | mFirstLeft = getListPaddingLeft();
107 | mFirstTop = firstChild.getTop();
108 | mColumnWidth = firstChild.getWidth();
109 | mRowHeight = firstChild.getHeight();
110 | int rowLine = firstVisibleItem / mColumnsNum;
111 | mScrollY = mFirstTop - rowLine * (mVerticalSpacing + mRowHeight);
112 | }
113 |
114 | @Override
115 | public void setOnItemLongClickListener(OnItemLongClickListener listener) {
116 | mOnItemLongClickListener = listener;
117 | }
118 |
119 | @Override
120 | public void setOnScrollListener(OnScrollListener l) {
121 | mOnScrollListener = l;
122 | }
123 |
124 | /**
125 | * If you want to draw something in gridview,just set a drawer.
126 | *
127 | * @param drawer a drawer.
128 | * @param drawOnTop true if you want draw on the top of Gridview.
129 | * @see IDrawer#onDraw(Canvas, int, int)
130 | */
131 | public void setDrawer(IDrawer drawer, boolean drawOnTop) {
132 | this.mDrawer = drawer;
133 | mDrawOnTop = drawOnTop;
134 | }
135 |
136 | /**
137 | * Tells the GridView whether to use Selector.
138 | * Nonuse selector by default.
139 | *
140 | * @param enabled true if use selector.
141 | */
142 | public void setSelectorEnabled(boolean enabled) {
143 | if (enabled != mUseSelector) {
144 | mUseSelector = enabled;
145 | if (mUseSelector && mSelector != null) {
146 | setSelector(mSelector);
147 | }
148 | if (!mUseSelector) {
149 | setSelector(getSelector());
150 | }
151 | }
152 |
153 | }
154 |
155 | public boolean isSelectorEnabled() {
156 | return mUseSelector;
157 | }
158 |
159 | @Override
160 | public void setSelector(Drawable sel) {
161 | if (mUseSelector) {
162 | super.setSelector(sel);
163 | } else {
164 | mSelector = sel;
165 | if (mSpaceDrawable == null) {
166 | mSpaceDrawable = new ColorDrawable();
167 | }
168 | super.setSelector(mSpaceDrawable);
169 | }
170 | }
171 |
172 | @Override
173 | public void setHorizontalSpacing(int horizontalSpacing) {
174 | super.setHorizontalSpacing(horizontalSpacing);
175 | mHorizontalSpacing = horizontalSpacing;
176 | }
177 |
178 | @Override
179 | public void setNumColumns(int numColumns) {
180 | super.setNumColumns(numColumns);
181 | mColumnsNum = numColumns;
182 | }
183 |
184 | @Override
185 | public void setClipToPadding(boolean clipToPadding) {
186 | super.setClipToPadding(clipToPadding);
187 | mClipToPadding = clipToPadding;
188 | }
189 |
190 | @Override
191 | public void setVerticalSpacing(int verticalSpacing) {
192 | super.setVerticalSpacing(verticalSpacing);
193 | mVerticalSpacing = verticalSpacing;
194 | }
195 |
196 | @Override
197 | public void setAdapter(ListAdapter adapter) {
198 | mAdapter = adapter;
199 | if (adapter instanceof OnItemMovedListener) {
200 | mItemMovedListener = (OnItemMovedListener) adapter;
201 | } else {
202 | //TODO should throw a exception here.
203 | log("Your adapter should implements OnItemMovedListener for listening item's swap action.");
204 | }
205 | super.setAdapter(mAdapter);
206 | }
207 |
208 | /**
209 | * When set, will change TOUCH mode to LONG_PRESS mode if the content of gridview can not scroll.
210 | * Set by default.
211 | *
212 | * @param autoOptimize
213 | */
214 | public void setAutoOptimize(boolean autoOptimize) {
215 | mAutoOptimize = autoOptimize;
216 | }
217 |
218 | public void setMode(MODE mode) {
219 | this.mode = mode;
220 | }
221 |
222 | /**
223 | * set the number of pixels gridview scrolled per second.
224 | *
225 | * @param scrollSpeed
226 | */
227 | public void setScrollSpeed(int scrollSpeed) {
228 | mScrollSpeed = scrollSpeed;
229 | }
230 |
231 | public int getDragPosition() {
232 | return mDraggedPosition;
233 | }
234 |
235 | private void refreshChildren() {
236 | int childCount = getChildCount();
237 | clearAllChildren();
238 | for (int i = 0; i < childCount; i++) {
239 | addChild(i, super.getChildAt(i));
240 | }
241 | }
242 |
243 | @Override
244 | protected void layoutChildren() {
245 | super.layoutChildren();
246 | // if (getChildCount() > 0) {
247 | // getBasicValues(getFirstVisiblePosition());
248 | // }
249 | if (mDraggedView != null) {
250 | refreshChildren();
251 | View draggedView = super.getChildAt(mDraggedPosition - mFirstVisibleFirstItem);
252 | dispatchItemReleased();
253 | mDraggedView = draggedView;
254 | dispatchItemCaptured();
255 | correctDraggedViewLocation(0, 0);
256 | } else {
257 | refreshChildren();
258 | }
259 | }
260 |
261 | @Override
262 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
263 | super.onLayout(changed, l, t, r, b);
264 | mGridViewVisibleRect = null;
265 | measureVisibleRect();
266 | }
267 |
268 | @Override
269 | protected void detachViewsFromParent(int start, int count) {
270 | super.detachViewsFromParent(start, count);
271 | if (start == 0) {
272 | for (int i = start; i < start + count; i++) {
273 | removeChild(0);
274 | }
275 | } else {
276 | start = mChildren.size() - 1;
277 | for (int i = start; i > start - count; i--) {
278 | removeChild(i);
279 | }
280 | }
281 | }
282 |
283 | @Override
284 | protected void attachViewToParent(View child, int index, ViewGroup.LayoutParams params) {
285 | super.attachViewToParent(child, index, params);
286 | addChild(index, child);
287 | }
288 |
289 | @Override
290 | protected void detachAllViewsFromParent() {
291 | super.detachAllViewsFromParent();
292 | clearAllChildren();
293 | }
294 |
295 | @Override
296 | public void onViewAdded(View child) {
297 | super.onViewAdded(child);
298 | int index = indexOfChild(child);
299 | addChild(index, child);
300 | }
301 |
302 | @Override
303 | public void onViewRemoved(View child) {
304 | super.onViewRemoved(child);
305 | removeChild(child);
306 | }
307 |
308 | private void clearAllChildren() {
309 | mChildren.clear();
310 | }
311 |
312 | private void addChild(int index, View view) {
313 | if (index < 0) {
314 | index = mChildren.size();
315 | }
316 | mChildren.add(index, view);
317 | }
318 |
319 | private boolean removeChild(View view) {
320 | boolean result = false;
321 | int childSize = mChildren.size();
322 | for (int i = 0; i < childSize; i++) {
323 | Child child = mChildren.get(i);
324 | if (child.view == view) {
325 | result = mChildren.remove(child);
326 | break;
327 | }
328 | }
329 | return result;
330 | }
331 |
332 | private void removeChild(int index) {
333 | mChildren.remove(index);
334 | }
335 |
336 | public boolean isTouchMode() {
337 | if (canScrollDown() || canScrollUp()) {
338 | // if the content of gridview can not scroll,change mode to LONG_PRESS for better user experience.
339 | if (mAutoOptimize) {
340 | mode = MODE.LONG_PRESS;
341 | }
342 | }
343 | return mode == MODE.TOUCH;
344 | }
345 |
346 | public boolean isNoneMode() {
347 | return mode == MODE.NONE;
348 | }
349 |
350 | public boolean isLongPressMode() {
351 | return mode == MODE.LONG_PRESS;
352 | }
353 |
354 | @Override
355 | public boolean onTouchEvent(MotionEvent ev) {
356 | return handleTouchEvent(ev);
357 | }
358 |
359 | private boolean handleTouchEvent(MotionEvent ev) {
360 | int action = ev.getAction();
361 | mCurrMotionEvent = ev;
362 | boolean handled = false;
363 | switch (action) {
364 | case MotionEvent.ACTION_DOWN:
365 | mLastMotionX = ev.getRawX();
366 | mLastMotionY = ev.getRawY();
367 | mShouldMove = false;
368 | // Debug.startMethodTracing("MoveOnGridView");
369 | if (isTouchMode()) {
370 | recordDragViewIfNeeded(null, -1);
371 | invalidate();
372 | if (mDraggedView != null) {
373 | mDraggedView.setPressed(true);
374 | }
375 | }
376 | break;
377 | case MotionEvent.ACTION_MOVE:
378 | int deltaX = (int) (ev.getRawX() - mLastMotionX);
379 | int deltaY = (int) (ev.getRawY() - mLastMotionY);
380 | if (mDraggedView != null) {
381 | handled = true;
382 | // intercept the MotionEvent only when user is not scrolling
383 | if (!mShouldMove) {
384 | // if (Math.abs(deltaX) > mTouchSlop || Math.abs(deltaY) > mTouchSlop) {
385 | // if (mDraggedView.isPressed()) {
386 | // mDraggedView.setPressed(false);
387 | // }
388 | // mShouldMove = true;
389 | // }
390 | if (mDraggedView.isPressed()) {
391 | mDraggedView.setPressed(false);
392 | }
393 | mShouldMove = true;
394 | }
395 | if (mShouldMove) {
396 | correctDraggedViewLocation(deltaX, deltaY);
397 | swapItemIfNeed(ev);
398 | scrollIfNeeded();
399 | }
400 | mLastMotionX = ev.getRawX();
401 | mLastMotionY = ev.getRawY();
402 | }
403 | break;
404 | case MotionEvent.ACTION_UP:
405 | case MotionEvent.ACTION_CANCEL:
406 | // Debug.stopMethodTracing();
407 | if (mDraggedView != null) {
408 | releaseDraggedView();
409 | mScrollRunner.cancel();
410 | handled = true;
411 | }
412 | // int motionPosition = getMotionPosition();
413 | mDraggedView = null;
414 | mCurrMotionEvent = null;
415 | // super.onTouchEvent(ev);
416 | break;
417 | }
418 | if (isTouchMode()) {
419 | handled = true;
420 | }
421 | boolean result = handled ? handled : super.onTouchEvent(ev);
422 | return result;
423 | }
424 |
425 | @Override
426 | public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
427 | boolean handled = false;
428 | if (isLongPressMode() && !isFixedPosition(position)) {
429 | recordDragViewIfNeeded(view, position);
430 | handled = true;
431 | }
432 | if (mOnItemLongClickListener != null) {
433 | boolean longClickReturn = mOnItemLongClickListener.onItemLongClick(parent, view, position, id);
434 | if (!handled) {
435 | handled = longClickReturn;
436 | }
437 | }
438 | return handled;
439 | }
440 |
441 | private int getMotionPosition() {
442 | final int realX = (int) (mCurrMotionEvent.getRawX() - mGridViewVisibleRect.left);
443 | final int realY = (int) (mCurrMotionEvent.getRawY() - mGridViewVisibleRect.top);
444 | return pointToPosition(realX, realY);
445 | }
446 |
447 | private void recordDragViewIfNeeded(View view, int position) {
448 | measureVisibleRect();
449 | if (view == null && position == -1) {
450 | int currDraggedPosition = getMotionPosition();
451 | if (currDraggedPosition != INVALID_POSITION) {
452 | recordDragViewIfNeeded(getChildAt(currDraggedPosition - mFirstVisibleFirstItem), currDraggedPosition);
453 | }
454 | } else {
455 | mDraggedPosition = position;
456 | mDraggedView = view;
457 | measureDraggedRect();
458 | mDraggedIndex = mDraggedPosition - mFirstVisibleFirstItem;
459 | dispatchItemCaptured();
460 | correctDraggedViewLocation(0, 0);
461 | }
462 | }
463 |
464 | @Override
465 | protected void dispatchDraw(Canvas canvas) {
466 | if (!mDrawOnTop) {
467 | drawSomeThing(canvas);
468 | }
469 | super.dispatchDraw(canvas);
470 | if (mDrawOnTop) {
471 | drawSomeThing(canvas);
472 | }
473 | }
474 |
475 | private void drawSomeThing(Canvas canvas) {
476 | if (mDrawer != null) {
477 | canvas.save();
478 | mDrawer.onDraw(canvas, getWidth(), getHeight());
479 | canvas.restore();
480 | }
481 | }
482 |
483 | @TargetApi(Build.VERSION_CODES.KITKAT)
484 | private void scrollIfNeeded() {
485 | measureDraggedRect();
486 | measureVisibleRect();
487 | if (!isDraggedInGridView()) {
488 | mScrollRunner.cancel();
489 | } else if (mDraggedRect.top <= mGridViewVisibleRect.top) {
490 | boolean canScrollDown = canScrollDown();
491 | if (canScrollDown && !mScrollRunner.isRunning()) {
492 | int deltaY = mClipToPadding ? mScrollY : mScrollY - getListPaddingTop();
493 | final int duration = Math.abs(deltaY) * 1000 / mScrollSpeed;
494 | mScrollRunner.start(0, deltaY, duration);
495 | }
496 | } else if (mDraggedRect.bottom >= mGridViewVisibleRect.bottom) {
497 | boolean canScrollUp = canScrollUp();
498 | if (canScrollUp && !mScrollRunner.isRunning()) {
499 | int deltaY = mClipToPadding ? getTotalScrollY() + mScrollY : getTotalScrollY() + mScrollY + getListPaddingBottom();
500 | final int duration = Math.abs(deltaY) * 1000 / mScrollSpeed;
501 | mScrollRunner.start(0, deltaY, duration);
502 | }
503 | } else {
504 | mScrollRunner.cancel();
505 | }
506 | }
507 |
508 | @TargetApi(Build.VERSION_CODES.KITKAT)
509 | public void scrollListBy(int deltaY) {
510 | if (SdkVerUtils.isAbove19()) {
511 | super.scrollListBy(deltaY);
512 | } else {
513 | ReflectUtil.invokeMethod(this, "trackMotionScroll", new Object[]{-deltaY, -deltaY}, new Class[]{int.class, int.class});
514 | }
515 | }
516 |
517 | @Override
518 | public void onMove(int lastX, int lastY, int curX, int curY) {
519 | int deltaY = curY - lastY;
520 | mDraggedView.offsetTopAndBottom(deltaY);
521 | scrollListBy(deltaY);
522 | swapItemIfNeed(mCurrMotionEvent);
523 | }
524 |
525 | @Override
526 | public void onDone() {
527 | }
528 |
529 | /**
530 | * detact whether the content of gridview can scroll down.
531 | *
532 | * @return
533 | */
534 | public boolean canScrollDown() {
535 | final int threshold = mClipToPadding ? 0 : getListPaddingTop();
536 | if (mScrollY < threshold) {
537 | return true;
538 | }
539 | return false;
540 | }
541 |
542 | /**
543 | * detact whether the content of gridview can scroll up.
544 | *
545 | * @return
546 | */
547 | public boolean canScrollUp() {
548 | final int threshold = mClipToPadding ? -mScrollY : getListPaddingBottom();
549 | if (getTotalScrollY() > -threshold) {
550 | return true;
551 | }
552 | return false;
553 | }
554 |
555 | public int getTotalScrollY() {
556 | if (mAdapter == null) return 0;
557 | int row = (mAdapter.getCount() - 1) / mColumnsNum + 1;
558 | int total = row * mRowHeight + (row - 1) * mVerticalSpacing;
559 | return total - getHeight();
560 | }
561 |
562 | private boolean isDraggedInGridView() {
563 | return mGridViewVisibleRect.intersects(mDraggedRect.left, mDraggedRect.top, mDraggedRect.right, mDraggedRect.bottom);
564 | }
565 |
566 | private void measureDraggedRect() {
567 | mDraggedView.getGlobalVisibleRect(mDraggedRect);
568 | int location[] = new int[2];
569 | mDraggedView.getLocationOnScreen(location);
570 | mDraggedRect.set(location[0], location[1], location[0] + mDraggedRect.width(), location[1] + mDraggedRect.height());
571 | }
572 |
573 | private void measureVisibleRect() {
574 | if (mGridViewVisibleRect == null) {
575 | mGridViewVisibleRect = new Rect();
576 | getGlobalVisibleRect(mGridViewVisibleRect);
577 | int location[] = new int[2];
578 | getLocationOnScreen(location);
579 | mGridViewVisibleRect.set(location[0], location[1], location[0] + mGridViewVisibleRect.width(),
580 | location[1] + mGridViewVisibleRect.height());
581 | }
582 | }
583 |
584 | private boolean isFixedPosition(int position) {
585 | if (position != INVALID_POSITION && mAdapter instanceof OnItemMovedListener) {
586 | mItemMovedListener = (OnItemMovedListener) mAdapter;
587 | if (mItemMovedListener.isFixed(position)) {
588 | return true;
589 | }
590 | return false;
591 | }
592 | return false;
593 | }
594 |
595 | private void swapItemIfNeed(MotionEvent ev) {
596 | if (ev == null || mDraggedView == null || isFixedPosition(mDraggedPosition)) return;
597 | measureVisibleRect();
598 | measureDraggedRect();
599 | final int realX = (int) (ev.getRawX() - mGridViewVisibleRect.left);
600 | final int realY = (int) (ev.getRawY() - mGridViewVisibleRect.top);
601 | int currDraggedPosition = pointToPosition(realX, realY);
602 | boolean intersect = isDraggedInGridView();
603 | int draggedViewPosition = INVALID_POSITION;
604 | if (currDraggedPosition != INVALID_POSITION) {
605 | if (intersect) {
606 | draggedViewPosition = currDraggedPosition;
607 | } else {
608 | draggedViewPosition = INVALID_POSITION;
609 | }
610 | }
611 | if (isFixedPosition(draggedViewPosition)) {
612 | draggedViewPosition = INVALID_POSITION;
613 | }
614 | if (draggedViewPosition != INVALID_POSITION) {
615 | int dragPosition = getDragPosition();
616 | if (draggedViewPosition != dragPosition) {
617 | measureDraggedRect();
618 | if (draggedViewPosition < dragPosition) {
619 | for (int i = dragPosition - 1; i >= draggedViewPosition; i--) {
620 | swapItem(i, i + 1);
621 | }
622 | } else {
623 | for (int i = dragPosition + 1; i <= draggedViewPosition; i++) {
624 | swapItem(i, i - 1);
625 | }
626 | }
627 | moveViewToPosition(draggedViewPosition, mDraggedView);
628 | mDraggedPosition = draggedViewPosition;
629 | }
630 | }
631 | }
632 |
633 | private void swapItem(int from, int to) {
634 | int fromIndex = from - getFirstVisiblePosition();
635 | Child fromChild = mChildren.get(fromIndex);
636 | final View fromView = fromChild.view;
637 | if (fromChild == null || fromView == null) return;
638 | fromChild.moveTo(from, to);
639 | moveViewToPosition(to, fromView);
640 | dispatchItemMoved(from, to);
641 | detachViewFromParent(fromView);
642 | super.attachViewToParent(fromView, to - getFirstVisiblePosition(), fromView.getLayoutParams());
643 | }
644 |
645 | private void dispatchItemMoved(int from, int to) {
646 | if (mItemMovedListener != null) {
647 | mItemMovedListener.onItemMoved(from, to);
648 | }
649 | }
650 |
651 | private void moveViewToPosition(int position, View view) {
652 | boolean removeResult = removeChild(view);
653 | addChild(getIndex(position), view);
654 | }
655 |
656 | private int getIndex(int position) {
657 | return position - mFirstVisibleFirstItem;
658 | }
659 |
660 | public int[] getLeftAndTopForPosition(int position) {
661 | int[] lt = new int[2];
662 | int m = position % mColumnsNum;
663 | int n = position / mColumnsNum;
664 | int left = mFirstLeft + m * (mColumnWidth + mHorizontalSpacing);
665 | int top = mScrollY + n * (mRowHeight + mVerticalSpacing);
666 | lt[0] = left;
667 | lt[1] = top;
668 | return lt;
669 | }
670 |
671 | @Override
672 | public View getChildAt(int index) {
673 | int position = index;
674 | final int childCount = getChildCount();
675 | if (mDraggedView != null) {
676 | int dragIndex = mDraggedPosition - mFirstVisibleFirstItem;
677 | if (dragIndex == 0) {
678 | if (index == 0) {
679 | position = 1;
680 | } else if (position == 1) {
681 | position = 0;
682 | } else {
683 | // position = position;
684 | }
685 |
686 | } else if (dragIndex == childCount - 1) {
687 | if (childCount % mColumnsNum != 1) {
688 | if (index == childCount - 1) {
689 | position = index - 1;
690 | } else if (index == childCount - 2) {
691 | position = childCount - 1;
692 | }
693 | }
694 | }
695 | }
696 | if (position >= getChildCount()) {
697 | position = getChildCount() - 1;
698 | }
699 | return super.getChildAt(position);
700 | }
701 |
702 | @Override
703 | public int getChildCount() {
704 | return super.getChildCount();
705 | }
706 |
707 | @Override
708 | public int pointToPosition(int x, int y) {
709 | if (mColumnWidth + mHorizontalSpacing <= 0 || mRowHeight + mVerticalSpacing == 0) {
710 | return INVALID_POSITION;
711 | }
712 | int m = (x - mFirstLeft) / (mColumnWidth + mHorizontalSpacing);
713 | int n = (y - mFirstTop) / (mRowHeight + mVerticalSpacing);
714 | int right = mFirstLeft + (m + 1) * (mColumnWidth + mHorizontalSpacing);
715 | int bottom = mFirstTop + (n + 1) * (mRowHeight + mVerticalSpacing) + mRowHeight;
716 | if (x > right || y > bottom || m >= mColumnsNum) {
717 | return INVALID_POSITION;
718 | } else {
719 | int result = n * mColumnsNum + m + mFirstVisibleFirstItem;
720 | result = result <= getLastVisiblePosition() ? result : INVALID_POSITION;
721 | return result;
722 | }
723 | }
724 |
725 | private void log(String msg) {
726 | Log.e("moveOnGridView", msg);
727 | }
728 |
729 | public void setOnItemClickListener(OnItemClickListener listener) {
730 | mOnItemClickListener = listener;
731 | }
732 |
733 | /**
734 | * Let our finger touch the center area of draggedView.
735 | */
736 | private void correctDraggedViewLocation(int deltaX, int deltaY) {
737 | if (mCurrMotionEvent == null) return;
738 | float motionX = mCurrMotionEvent.getRawX();
739 | float motionY = mCurrMotionEvent.getRawY();
740 | measureVisibleRect();
741 | deltaX = (int) (motionX - mGridViewVisibleRect.left - (mDraggedView.getLeft() + mColumnWidth / 2)) + deltaX;
742 | deltaY = (int) (motionY - mGridViewVisibleRect.top - (mDraggedView.getTop() + mRowHeight / 2)) + deltaY;
743 | if (!isFixedPosition(mDraggedPosition)) {
744 | mDraggedView.offsetLeftAndRight(deltaX);
745 | mDraggedView.offsetTopAndBottom(deltaY);
746 | }
747 |
748 | }
749 |
750 | private void dispatchItemCaptured() {
751 | if (mItemCapturedListener != null && !isFixedPosition(mDraggedPosition)) {
752 | mItemCapturedListener.onItemCaptured(mDraggedView, mDraggedPosition);
753 | }
754 | }
755 |
756 | private void releaseDraggedView() {
757 | int[] destination = getLeftAndTopForPosition(mDraggedPosition);
758 | int offsetX = destination[0] - mDraggedView.getLeft();
759 | int offsetY = destination[1] - mDraggedView.getTop();
760 | mDraggedView.offsetLeftAndRight(offsetX);
761 | mDraggedView.offsetTopAndBottom(offsetY);
762 | dispatchItemReleased();
763 | if (mDraggedView.isPressed()) {
764 | mDraggedView.setPressed(false);
765 | }
766 | }
767 |
768 | private void dispatchItemReleased() {
769 | if (mItemCapturedListener != null && !isFixedPosition(mDraggedPosition)) {
770 | mItemCapturedListener.onItemReleased(mDraggedView, mDraggedPosition);
771 | }
772 | }
773 |
774 | private OnItemCapturedListener mItemCapturedListener;
775 |
776 | public void setOnItemCapturedListener(OnItemCapturedListener listener) {
777 | mItemCapturedListener = listener;
778 | }
779 |
780 | @Override
781 | protected int getChildDrawingOrder(int childCount, int i) {
782 | int order = i;
783 | if (mDraggedView != null) {
784 | mDraggedIndex = mDraggedPosition - mFirstVisibleFirstItem;
785 | if (i == mDraggedIndex) {
786 | order = childCount - 1;
787 | } else if (i == childCount - 1) {
788 | order = mDraggedIndex;
789 | }
790 | }
791 | return order;
792 | }
793 |
794 | public MODE getMode() {
795 | return mode;
796 | }
797 |
798 | @Override
799 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
800 | if (mOnItemClickListener != null) {
801 | mOnItemClickListener.onItemClick(parent, view, position, id);
802 | }
803 | }
804 |
805 | public enum MODE {
806 | TOUCH, LONG_PRESS, NONE;
807 |
808 | public static int indexOf(MODE mode) {
809 | int index = -1;
810 | for (MODE mode2 : MODE.values()) {
811 | index++;
812 | if (mode == mode2) {
813 | break;
814 | }
815 | }
816 | return index;
817 | }
818 |
819 | public static MODE get(int index) {
820 | int i = 0;
821 | for (MODE mode : MODE.values()) {
822 | if (i == index) {
823 | return mode;
824 | }
825 | i++;
826 | }
827 | return null;
828 | }
829 |
830 | @Override
831 | public String toString() {
832 | String name = name();
833 | switch (name) {
834 | case "TOUCH":
835 | return "编辑模式";
836 | case "LONG_PRESS":
837 | return "长按拖拽模式";
838 | case "NONE":
839 | return "普通模式";
840 | }
841 | return super.toString();
842 | }
843 | }
844 | }
--------------------------------------------------------------------------------