├── .idea
├── .name
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── vcs.xml
├── modules.xml
├── gradle.xml
├── compiler.xml
└── misc.xml
├── sample
├── .gitignore
├── sample_in_action.gif
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable-hdpi
│ │ │ ├── ic_drag.png
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_action_new_holo_light.png
│ │ ├── drawable-mdpi
│ │ │ ├── ic_drag.png
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_action_new_holo_light.png
│ │ ├── drawable-xhdpi
│ │ │ ├── ic_drag.png
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_action_new_holo_light.png
│ │ ├── drawable-xxhdpi
│ │ │ ├── ic_drag.png
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_action_new_holo_light.png
│ │ ├── values
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ ├── drawable-nodpi
│ │ │ └── cherry_blossom.jpg
│ │ ├── values-v21
│ │ │ └── styles.xml
│ │ ├── values-v16
│ │ │ └── styles.xml
│ │ ├── drawable
│ │ │ └── selector_white_button.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── layout
│ │ │ ├── activity_note.xml
│ │ │ ├── activity_demo.xml
│ │ │ └── list_item_note.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── jmedeisis
│ │ └── example
│ │ └── draglinearlayout
│ │ ├── DemoActivity.java
│ │ └── NoteActivity.java
├── build.gradle
├── proguard-rules.pro
└── sample.iml
├── library
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── dimens.xml
│ │ │ └── attrs.xml
│ │ ├── drawable-hdpi
│ │ │ └── ab_solid_shadow_holo.9.png
│ │ ├── drawable-mdpi
│ │ │ └── ab_solid_shadow_holo.9.png
│ │ ├── drawable-xhdpi
│ │ │ └── ab_solid_shadow_holo.9.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ab_solid_shadow_holo.9.png
│ │ └── drawable
│ │ │ └── ab_solid_shadow_holo_flipped.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── jmedeisis
│ │ └── draglinearlayout
│ │ └── DragLinearLayout.java
├── proguard-rules.pro
├── build.gradle
└── library.iml
├── settings.gradle
├── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── DragLinearLayout.iml
├── LICENSE
├── gradlew.bat
├── README.md
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | DragLinearLayout
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sample', ':library'
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | .DS_Store
6 | /build
7 |
--------------------------------------------------------------------------------
/sample/sample_in_action.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/sample_in_action.gif
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_drag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-hdpi/ic_drag.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_drag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-mdpi/ic_drag.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_drag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-xhdpi/ic_drag.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_drag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-xxhdpi/ic_drag.png
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #eee
4 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-nodpi/cherry_blossom.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-nodpi/cherry_blossom.jpg
--------------------------------------------------------------------------------
/library/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable-hdpi/ab_solid_shadow_holo.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/library/src/main/res/drawable-hdpi/ab_solid_shadow_holo.9.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-mdpi/ab_solid_shadow_holo.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/library/src/main/res/drawable-mdpi/ab_solid_shadow_holo.9.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-xhdpi/ab_solid_shadow_holo.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/library/src/main/res/drawable-xhdpi/ab_solid_shadow_holo.9.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-xxhdpi/ab_solid_shadow_holo.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/library/src/main/res/drawable-xxhdpi/ab_solid_shadow_holo.9.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_action_new_holo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-hdpi/ic_action_new_holo_light.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_action_new_holo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-mdpi/ic_action_new_holo_light.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_action_new_holo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-xhdpi/ic_action_new_holo_light.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_action_new_holo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justasm/DragLinearLayout/HEAD/sample/src/main/res/drawable-xxhdpi/ic_action_new_holo_light.png
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-v16/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jan 06 18:11:16 GMT 2015
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-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/selector_white_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/ab_solid_shadow_holo_flipped.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 | 16dp
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 16dp
6 | 16dp
7 | 8dp
8 | 8dp
9 | 4dp
10 | 4dp
11 | 16dp
12 | 8dp
13 | 48dp
14 |
15 |
16 |
--------------------------------------------------------------------------------
/library/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/Justas/Desktop/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 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion '22.0.1'
6 |
7 | defaultConfig {
8 | applicationId "com.example.draglinearlayout"
9 | minSdkVersion 15
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile project(":library")
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | compile 'com.android.support:appcompat-v7:22.1.1'
26 | }
27 |
--------------------------------------------------------------------------------
/sample/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/Justas/Desktop/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 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Settings specified in this file will override any Gradle settings
5 | # configured through the IDE.
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
--------------------------------------------------------------------------------
/DragLinearLayout.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Justas Medeisis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
23 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'bintray-release'
3 |
4 | android {
5 | compileSdkVersion 22
6 | buildToolsVersion '22.0.1'
7 |
8 | defaultConfig {
9 | minSdkVersion 14
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile 'com.android.support:support-v4:22.1.1'
24 | }
25 |
26 | buildscript {
27 | repositories {
28 | jcenter()
29 | }
30 |
31 | dependencies {
32 | classpath 'com.novoda:bintray-release:0.3.4'
33 | }
34 | }
35 |
36 | // https://github.com/novoda/bintray-release/wiki/Configuration-of-the-publish-closure
37 | publish {
38 | userOrg = 'justasm'
39 | groupId = 'com.jmedeisis'
40 | artifactId = 'draglinearlayout'
41 | version = "1.1.0"
42 | licences = ['MIT']
43 | description = "Android LinearLayout with drag and drop to reorder."
44 | website = 'https://github.com/justasm/DragLinearLayout'
45 | }
--------------------------------------------------------------------------------
/sample/src/main/java/com/jmedeisis/example/draglinearlayout/DemoActivity.java:
--------------------------------------------------------------------------------
1 | package com.jmedeisis.example.draglinearlayout;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.View;
7 |
8 | import com.jmedeisis.draglinearlayout.DragLinearLayout;
9 |
10 | public class DemoActivity extends AppCompatActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_demo);
16 |
17 | DragLinearLayout dragLinearLayout = (DragLinearLayout) findViewById(R.id.container);
18 | // set all children draggable except the first (the header)
19 | for(int i = 1; i < dragLinearLayout.getChildCount(); i++){
20 | View child = dragLinearLayout.getChildAt(i);
21 | dragLinearLayout.setViewDraggable(child, child); // the child is its own drag handle
22 | }
23 |
24 | findViewById(R.id.noteDemoButton).setOnClickListener(new View.OnClickListener() {
25 | @Override
26 | public void onClick(View v) {
27 | startActivity(new Intent(DemoActivity.this, NoteActivity.class));
28 | }
29 | });
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DragLinearLayout Demo
5 | Simple linear layout
6 | Drag me! Drop me!
7 | Swap with me! Swap with me!
8 | I\'m quite sensitive\nabout my height.
9 | I heard that one of us is a Button!
10 | Someone forgot to make me draggable…
11 | Linear layout within ScrollView
12 | Icon.
13 | Add new note
14 | Write note
15 | View DragLinearLayout inside ScrollView demo
16 | Demo image
17 |
18 |
19 | - I am an EditText. Tap me to edit me!
20 | - I deserve priority! Grab my handle on the left and drag me higher!
21 |
22 |
23 | Drag&Drop Notes Demo
24 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_note.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
9 |
10 |
11 |
16 |
17 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_demo.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
15 |
16 |
21 |
22 |
27 |
28 |
33 |
34 |
40 |
41 |
46 |
47 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/list_item_note.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
28 |
29 |
30 |
35 |
36 |
46 |
47 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
19 |
20 |
21 |
32 |
33 |
43 |
44 |
45 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | DragLinearLayout
2 | ================
3 |
4 | 
5 |
6 | An Android `LinearLayout` that supports draggable and swappable child `View`s.
7 |
8 | Why?
9 | ----
10 | Why bother doing drag & swap in a `LinearLayout` [when][drag_list_1] [there are][drag_list_2]
11 | [so many][drag_list_3] [solutions][drag_list_4] for `ListView`?
12 |
13 | 1. *Simplicity* - no need for `ListAdapter`s. By default, works like a `LinearLayout`.
14 | 2. *Flexibility* - supports heterogeneous, selectively draggable (or not draggable), children.
15 | 3. *Portability* - can be used in any layout, e.g. as a child in a `ScrollView` container.
16 |
17 | Usage
18 | -----
19 | Add it to your project using Gradle:
20 |
21 | ```groovy
22 | compile 'com.jmedeisis:draglinearlayout:1.1.0'
23 | ```
24 |
25 | The `DragLinearLayout` can be used in place of any `LinearLayout`. However, by default, children
26 | will not be draggable. To set an existing `View` as draggable, use
27 | `DragLinearLayout#setViewDraggable(View, View)`, passing in the child `View` and a (non-null!)
28 | `View` that will act as the handle for dragging it (this can be the `View` itself).
29 |
30 | XML layout file:
31 |
32 | ```xml
33 |
38 |
39 |
43 |
44 |
49 |
50 |
55 |
56 |
57 | ```
58 |
59 | Enabling drag & swap for all child views:
60 |
61 | ```java
62 | DragLinearLayout dragLinearLayout = (DragLinearLayout) findViewById(R.id.container);
63 | for(int i = 0; i < dragLinearLayout.getChildCount(); i++){
64 | View child = dragLinearLayout.getChildAt(i);
65 | // the child will act as its own drag handle
66 | dragLinearLayout.setViewDraggable(child, child);
67 | }
68 | ```
69 |
70 | Use `#addDragView(View, View)`,`#addDragView(View, View, int)` and `#removeDragView(View)` to
71 | manage draggable children dynamically:
72 |
73 | ```java
74 | final View view = View.inflate(context, R.layout.view_layout, null);
75 | dragLinearLayout.addDragView(view, view.findViewById(R.id.view_drag_handle));
76 |
77 | // ..
78 |
79 | dragLinearLayout.removeDragView(view);
80 | ```
81 |
82 | Attach an `OnViewSwapListener` with `#setOnViewSwapListener(OnViewSwapListener)` to detect changes
83 | to the ordering of child `View`s:
84 |
85 | ```java
86 | dragLinearLayout.setOnViewSwapListener(new DragLinearLayout.OnViewSwapListener() {
87 | @Override
88 | public void onSwap(View firstView, int firstPosition,
89 | View secondView, int secondPosition) {
90 | // update data, etc..
91 | }
92 | });
93 | ```
94 |
95 | When placing the `DragLinearLayout` inside a `ScrollView`, call `#setContainerScrollView(ScrollView)`
96 | to enable the user to scroll while dragging a child view.
97 |
98 | For best visual results, use children that have opaque backgrounds. Furthermore, do not use
99 | horizontal padding for the `DragLinearLayout`; instead, let children apply their own horizontal
100 | padding.
101 |
102 | Refer to the included sample activity project for a demonstration of the above usage techniques
103 | and more.
104 |
105 | Limitations
106 | -----------
107 | - Supports only the `LinearLayout#VERTICAL` orientation.
108 |
109 | License
110 | -------
111 | This project is licensed under the terms of the MIT license.
112 | You may find a copy of the license in the included `LICENSE` file.
113 |
114 | [drag_list_1]: https://github.com/bauerca/drag-sort-listview
115 | [drag_list_2]: https://plus.google.com/u/0/+AndroidDevelopers/posts/7Qo9vmeqKwC
116 | [drag_list_3]: http://ericharlow.blogspot.com/2010/10/experience-android-drag-and-drop-list.html
117 | [drag_list_4]: https://github.com/terlici/DragNDropList
118 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/library/library.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | generateDebugAndroidTestSources
19 | generateDebugSources
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/sample/sample.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | generateDebugAndroidTestSources
19 | generateDebugSources
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/jmedeisis/example/draglinearlayout/NoteActivity.java:
--------------------------------------------------------------------------------
1 | package com.jmedeisis.example.draglinearlayout;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.ObjectAnimator;
7 | import android.content.Context;
8 | import android.os.Bundle;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.text.Editable;
11 | import android.text.InputType;
12 | import android.text.TextWatcher;
13 | import android.view.View;
14 | import android.view.ViewTreeObserver;
15 | import android.view.inputmethod.InputMethodManager;
16 | import android.widget.EditText;
17 | import android.widget.ImageView;
18 | import android.widget.ScrollView;
19 | import android.widget.Toast;
20 |
21 | import com.jmedeisis.draglinearlayout.DragLinearLayout;
22 |
23 | public class NoteActivity extends AppCompatActivity {
24 |
25 | DragLinearLayout noteContainer;
26 | int firstNoteIndex;
27 | int noteCount;
28 | ScrollView scrollView;
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | setContentView(R.layout.activity_note);
34 |
35 | noteContainer = (DragLinearLayout) findViewById(R.id.noteContainer);
36 | String[] defaultNotes = getResources().getStringArray(R.array.notes);
37 | firstNoteIndex = noteContainer.getChildCount();
38 | for(int i = 0; i < defaultNotes.length; i++){
39 | addExistingNote(i, defaultNotes[i]);
40 | }
41 | scrollView = (ScrollView) findViewById(R.id.scrollView);
42 | noteContainer.setContainerScrollView(scrollView);
43 | noteContainer.setOnViewSwapListener(new DragLinearLayout.OnViewSwapListener() {
44 | @Override
45 | public void onSwap(View firstView, int firstPosition, View secondView, int secondPosition) {
46 | final int firstIndex = getNoteIndex(firstView);
47 | final int secondIndex = getNoteIndex(secondView);
48 | setNoteIndex(firstView, secondIndex);
49 | setNoteIndex(secondView, firstIndex);
50 |
51 | Toast.makeText(NoteActivity.this, "Swapped note " + firstIndex + " with note " +
52 | secondIndex, Toast.LENGTH_SHORT).show();
53 | }
54 | });
55 |
56 | noteCount = defaultNotes.length;
57 | addNewBlankNote(noteCount, false);
58 | }
59 |
60 | private void addExistingNote(int index, String text){
61 | final View note = View.inflate(this, R.layout.list_item_note, null);
62 | ((ImageView) note.findViewById(R.id.noteIcon)).setImageResource(R.drawable.ic_drag);
63 |
64 | setNoteIndex(note, index);
65 |
66 | final EditText noteText = (EditText) note.findViewById(R.id.noteText);
67 | noteText.setText(text);
68 | noteText.addTextChangedListener(new NoteTextWatcher(note));
69 | noteText.setOnFocusChangeListener(new NoteFocusChangeListener(note));
70 | noteText.setHint(R.string.note_complete_prompt);
71 |
72 | noteContainer.addDragView(note, note.findViewById(R.id.noteIconContainer));
73 | }
74 |
75 | private void addNewBlankNote(int index, boolean animateIn){
76 | final View note = View.inflate(this, R.layout.list_item_note, null);
77 |
78 | setNoteIndex(note, index);
79 |
80 | final EditText noteText = (EditText) note.findViewById(R.id.noteText);
81 | noteText.addTextChangedListener(new NoteTextWatcher(note));
82 | noteText.setOnFocusChangeListener(new NoteFocusChangeListener(note));
83 |
84 | noteContainer.addView(note);
85 |
86 | if(animateIn){
87 | final ViewTreeObserver observer = note.getViewTreeObserver();
88 | observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
89 | @Override
90 | public boolean onPreDraw() {
91 | observer.removeOnPreDrawListener(this);
92 |
93 | ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(note, "alpha", 0, 1);
94 | ObjectAnimator yAnimator = ObjectAnimator.ofFloat(note, "y",
95 | note.getTop() + 20, note.getTop());
96 | AnimatorSet set = new AnimatorSet();
97 | set.playTogether(alphaAnimator, yAnimator);
98 | set.setDuration(200);
99 | set.start();
100 |
101 | scrollView.smoothScrollBy(0, note.getHeight());
102 | return true;
103 | }
104 |
105 | });
106 | }
107 | }
108 |
109 | /** Animates in a new blank note if text is entered. */
110 | private class NoteTextWatcher implements TextWatcher {
111 | private final View note;
112 | public NoteTextWatcher(final View note){
113 | this.note = note;
114 | }
115 |
116 | @Override
117 | public void afterTextChanged(Editable s) { }
118 | @Override
119 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
120 | @Override
121 | public void onTextChanged(CharSequence s, int start, int before, int count) {
122 | if(0 != start) return;
123 | if(count > 0 && before < 1){
124 | final int index = getNoteIndex(note);
125 | if(noteCount == index && getLastNoteIndex() == index){
126 | addNewBlankNote(noteCount + 1, true); // not a very 'nice' way to pick next index..
127 | }
128 | } else if(0 == count && before > 0){
129 | // TODO remove note on additional backspace??
130 | }
131 | }
132 | }
133 |
134 | /** Toggles IME visibility and stores state on focus. */
135 | private class NoteFocusChangeListener implements View.OnFocusChangeListener {
136 | final View note;
137 |
138 | public NoteFocusChangeListener(View note){
139 | this.note = note;
140 | }
141 |
142 | @Override
143 | public void onFocusChange(View v, boolean hasFocus) {
144 | setImeVisibility(noteContainer, hasFocus);
145 | EditText editText = (EditText) v;
146 | final int index = getNoteIndex(note);
147 |
148 | if(hasFocus){
149 | editText.setRawInputType(InputType.TYPE_CLASS_TEXT |
150 | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
151 | } else {
152 | if(editText.length() > 0){
153 | ((ImageView)note.findViewById(R.id.noteIcon)).setImageResource(R.drawable.ic_drag);
154 | noteContainer.setViewDraggable(note, note.findViewById(R.id.noteIconContainer));
155 |
156 | if(index < noteCount){
157 | // note at index set to new value
158 | } else {
159 | // new note added at index
160 | noteCount++;
161 | }
162 |
163 | editText.setHint(R.string.note_complete_prompt);
164 | } else {
165 | if(index < noteCount){
166 | // existing note set blank
167 | } else if(index < getLastNoteIndex()){
168 | // too many trailing blank notes, remove last one
169 | final View noteToDelete = noteContainer.getChildAt(firstNoteIndex + index + 1);
170 | noteToDelete.findViewById(R.id.noteText).setEnabled(false); // disable further editing
171 |
172 | ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(noteToDelete, "alpha", 1, 0);
173 | ObjectAnimator xAnimator = ObjectAnimator.ofFloat(noteToDelete, "x",
174 | noteToDelete.getLeft(), noteToDelete.getLeft() + 30);
175 | AnimatorSet set = new AnimatorSet();
176 | set.playTogether(alphaAnimator, xAnimator);
177 | set.setDuration(200);
178 | set.addListener(new AnimatorListenerAdapter(){
179 | @Override
180 | public void onAnimationEnd(Animator animation) {
181 | noteContainer.removeView(noteToDelete);
182 | }
183 | });
184 | set.start();
185 | }
186 | }
187 | editText.setRawInputType(InputType.TYPE_CLASS_TEXT |
188 | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
189 | }
190 | }
191 | }
192 |
193 | /** Stores the index as the View's tag. */
194 | private void setNoteIndex(View note, int index){
195 | note.setTag(index);
196 | }
197 | /** @return the index stored as the View's tag, or -1 if a tag is not present. */
198 | private int getNoteIndex(View note){
199 | final Object tag = note.getTag();
200 | if(null == tag) return -1;
201 |
202 | return (Integer) tag;
203 | }
204 |
205 | private int getLastNoteIndex(){
206 | return noteContainer.getChildCount() - 1 - firstNoteIndex;
207 | }
208 |
209 | /*
210 | * IME fiddling
211 | * ---------------------------------------------------------------------------------------------
212 | */
213 | private Runnable toggleImeRunnable;
214 | /**
215 | * Opens or closes the IME.
216 | * If called in quick succession (i.e. before the message queue of the view is processed),
217 | * only the latest call will get executed.
218 | */
219 | private void setImeVisibility(final View view, final boolean visible) {
220 | if (visible) {
221 | view.removeCallbacks(toggleImeRunnable);
222 | toggleImeRunnable = new Runnable() {
223 | public void run() {
224 | InputMethodManager imm = (InputMethodManager)
225 | NoteActivity.this.getSystemService(Context.INPUT_METHOD_SERVICE);
226 |
227 | if (null != imm) {
228 | imm.showSoftInput(view, 0);
229 | }
230 | }
231 | };
232 | view.post(toggleImeRunnable);
233 | } else {
234 | view.removeCallbacks(toggleImeRunnable);
235 | toggleImeRunnable = new Runnable() {
236 | public void run() {
237 | InputMethodManager imm = (InputMethodManager)
238 | NoteActivity.this.getSystemService(Context.INPUT_METHOD_SERVICE);
239 |
240 | if (null != imm) {
241 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
242 | }
243 | }
244 | };
245 | view.post(toggleImeRunnable);
246 | }
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jmedeisis/draglinearlayout/DragLinearLayout.java:
--------------------------------------------------------------------------------
1 | package com.jmedeisis.draglinearlayout;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.LayoutTransition;
6 | import android.animation.ObjectAnimator;
7 | import android.animation.ValueAnimator;
8 | import android.content.Context;
9 | import android.content.res.Resources;
10 | import android.content.res.TypedArray;
11 | import android.graphics.Bitmap;
12 | import android.graphics.Canvas;
13 | import android.graphics.Rect;
14 | import android.graphics.drawable.BitmapDrawable;
15 | import android.graphics.drawable.Drawable;
16 | import android.support.annotation.NonNull;
17 | import android.support.v4.content.ContextCompat;
18 | import android.support.v4.view.MotionEventCompat;
19 | import android.util.AttributeSet;
20 | import android.util.Log;
21 | import android.util.SparseArray;
22 | import android.view.MotionEvent;
23 | import android.view.View;
24 | import android.view.ViewConfiguration;
25 | import android.view.ViewTreeObserver;
26 | import android.view.ViewTreeObserver.OnPreDrawListener;
27 | import android.widget.LinearLayout;
28 | import android.widget.ScrollView;
29 |
30 | /**
31 | * A LinearLayout that supports children Views that can be dragged and swapped around.
32 | * See {@link #addDragView(android.view.View, android.view.View)},
33 | * {@link #addDragView(android.view.View, android.view.View, int)},
34 | * {@link #setViewDraggable(android.view.View, android.view.View)}, and
35 | * {@link #removeDragView(android.view.View)}.
36 | *
37 | * Currently, no error-checking is done on standard {@link #addView(android.view.View)} and
38 | * {@link #removeView(android.view.View)} calls, so avoid using these with children previously
39 | * declared as draggable to prevent memory leaks and/or subtle bugs. Pull requests welcome!
40 | */
41 | public class DragLinearLayout extends LinearLayout {
42 | private static final String LOG_TAG = DragLinearLayout.class.getSimpleName();
43 | private static final long NOMINAL_SWITCH_DURATION = 150;
44 | private static final long MIN_SWITCH_DURATION = NOMINAL_SWITCH_DURATION;
45 | private static final long MAX_SWITCH_DURATION = NOMINAL_SWITCH_DURATION * 2;
46 | private static final float NOMINAL_DISTANCE = 20;
47 | private final float nominalDistanceScaled;
48 |
49 | /**
50 | * Use with {@link com.jmedeisis.draglinearlayout.DragLinearLayout#setOnViewSwapListener(com.jmedeisis.draglinearlayout.DragLinearLayout.OnViewSwapListener)}
51 | * to listen for draggable view swaps.
52 | */
53 | public interface OnViewSwapListener {
54 | /**
55 | * Invoked right before the two items are swapped due to a drag event.
56 | * After the swap, the firstView will be in the secondPosition, and vice versa.
57 | *
58 | * No guarantee is made as to which of the two has a lesser/greater position.
59 | */
60 | void onSwap(View firstView, int firstPosition, View secondView, int secondPosition);
61 | }
62 |
63 | private OnViewSwapListener swapListener;
64 |
65 | private LayoutTransition layoutTransition;
66 |
67 | /**
68 | * Mapping from child index to drag-related info container.
69 | * Presence of mapping implies the child can be dragged, and is considered for swaps with the
70 | * currently dragged item.
71 | */
72 | private final SparseArray draggableChildren;
73 |
74 | private class DraggableChild {
75 | /**
76 | * If non-null, a reference to an on-going position animation.
77 | */
78 | private ValueAnimator swapAnimation;
79 |
80 | public void endExistingAnimation() {
81 | if (null != swapAnimation) swapAnimation.end();
82 | }
83 |
84 | public void cancelExistingAnimation() {
85 | if (null != swapAnimation) swapAnimation.cancel();
86 | }
87 | }
88 |
89 | /**
90 | * Holds state information about the currently dragged item.
91 | *
92 | * Rough lifecycle:
93 | * #startDetectingOnPossibleDrag - #detecting == true
94 | * if drag is recognised, #onDragStart - #dragging == true
95 | * if drag ends, #onDragStop - #dragging == false, #settling == true
96 | * if gesture ends without drag, or settling finishes, #stopDetecting - #detecting == false
97 | */
98 | private class DragItem {
99 | private View view;
100 | private int startVisibility;
101 | private BitmapDrawable viewDrawable;
102 | private int position;
103 | private int startTop;
104 | private int height;
105 | private int totalDragOffset;
106 | private int targetTopOffset;
107 | private ValueAnimator settleAnimation;
108 |
109 | private boolean detecting;
110 | private boolean dragging;
111 |
112 | public DragItem() {
113 | stopDetecting();
114 | }
115 |
116 | public void startDetectingOnPossibleDrag(final View view, final int position) {
117 | this.view = view;
118 | this.startVisibility = view.getVisibility();
119 | this.viewDrawable = getDragDrawable(view);
120 | this.position = position;
121 | this.startTop = view.getTop();
122 | this.height = view.getHeight();
123 | this.totalDragOffset = 0;
124 | this.targetTopOffset = 0;
125 | this.settleAnimation = null;
126 |
127 | this.detecting = true;
128 | }
129 |
130 | public void onDragStart() {
131 | view.setVisibility(View.INVISIBLE);
132 | this.dragging = true;
133 | }
134 |
135 | public void setTotalOffset(int offset) {
136 | totalDragOffset = offset;
137 | updateTargetTop();
138 | }
139 |
140 | public void updateTargetTop() {
141 | targetTopOffset = startTop - view.getTop() + totalDragOffset;
142 | }
143 |
144 | public void onDragStop() {
145 | this.dragging = false;
146 | }
147 |
148 | public boolean settling() {
149 | return null != settleAnimation;
150 | }
151 |
152 | public void stopDetecting() {
153 | this.detecting = false;
154 | if (null != view) view.setVisibility(startVisibility);
155 | view = null;
156 | startVisibility = -1;
157 | viewDrawable = null;
158 | position = -1;
159 | startTop = -1;
160 | height = -1;
161 | totalDragOffset = 0;
162 | targetTopOffset = 0;
163 | if (null != settleAnimation) settleAnimation.end();
164 | settleAnimation = null;
165 | }
166 | }
167 |
168 | /**
169 | * The currently dragged item, if {@link com.jmedeisis.draglinearlayout.DragLinearLayout.DragItem#detecting}.
170 | */
171 | private final DragItem draggedItem;
172 | private final int slop;
173 |
174 | private static final int INVALID_POINTER_ID = -1;
175 | private int downY = -1;
176 | private int activePointerId = INVALID_POINTER_ID;
177 |
178 | /**
179 | * The shadow to be drawn above the {@link #draggedItem}.
180 | */
181 | private final Drawable dragTopShadowDrawable;
182 | /**
183 | * The shadow to be drawn below the {@link #draggedItem}.
184 | */
185 | private final Drawable dragBottomShadowDrawable;
186 | private final int dragShadowHeight;
187 |
188 | /**
189 | * See {@link #setContainerScrollView(android.widget.ScrollView)}.
190 | */
191 | private ScrollView containerScrollView;
192 | private int scrollSensitiveAreaHeight;
193 | private static final int DEFAULT_SCROLL_SENSITIVE_AREA_HEIGHT_DP = 48;
194 | private static final int MAX_DRAG_SCROLL_SPEED = 16;
195 |
196 | public DragLinearLayout(Context context) {
197 | this(context, null);
198 | }
199 |
200 | public DragLinearLayout(Context context, AttributeSet attrs) {
201 | super(context, attrs);
202 |
203 | setOrientation(LinearLayout.VERTICAL);
204 |
205 | draggableChildren = new SparseArray<>();
206 |
207 | draggedItem = new DragItem();
208 | ViewConfiguration vc = ViewConfiguration.get(context);
209 | slop = vc.getScaledTouchSlop();
210 |
211 | final Resources resources = getResources();
212 | dragTopShadowDrawable = ContextCompat.getDrawable(context, R.drawable.ab_solid_shadow_holo_flipped);
213 | dragBottomShadowDrawable = ContextCompat.getDrawable(context, R.drawable.ab_solid_shadow_holo);
214 | dragShadowHeight = resources.getDimensionPixelSize(R.dimen.downwards_drop_shadow_height);
215 |
216 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DragLinearLayout, 0, 0);
217 | try {
218 | scrollSensitiveAreaHeight = a.getDimensionPixelSize(R.styleable.DragLinearLayout_scrollSensitiveHeight,
219 | (int) (DEFAULT_SCROLL_SENSITIVE_AREA_HEIGHT_DP * resources.getDisplayMetrics().density + 0.5f));
220 | } finally {
221 | a.recycle();
222 | }
223 |
224 | nominalDistanceScaled = (int) (NOMINAL_DISTANCE * resources.getDisplayMetrics().density + 0.5f);
225 | }
226 |
227 | @Override
228 | public void setOrientation(int orientation) {
229 | // enforce VERTICAL orientation; remove if HORIZONTAL support is ever added
230 | if (LinearLayout.HORIZONTAL == orientation) {
231 | throw new IllegalArgumentException("DragLinearLayout must be VERTICAL.");
232 | }
233 | super.setOrientation(orientation);
234 | }
235 |
236 | /**
237 | * Calls {@link #addView(android.view.View)} followed by {@link #setViewDraggable(android.view.View, android.view.View)}.
238 | */
239 | public void addDragView(View child, View dragHandle) {
240 | addView(child);
241 | setViewDraggable(child, dragHandle);
242 | }
243 |
244 | /**
245 | * Calls {@link #addView(android.view.View, int)} followed by
246 | * {@link #setViewDraggable(android.view.View, android.view.View)} and correctly updates the
247 | * drag-ability state of all existing views.
248 | */
249 | public void addDragView(View child, View dragHandle, int index) {
250 | addView(child, index);
251 |
252 | // update drag-able children mappings
253 | final int numMappings = draggableChildren.size();
254 | for (int i = numMappings - 1; i >= 0; i--) {
255 | final int key = draggableChildren.keyAt(i);
256 | if (key >= index) {
257 | draggableChildren.put(key + 1, draggableChildren.get(key));
258 | }
259 | }
260 |
261 | setViewDraggable(child, dragHandle);
262 | }
263 |
264 | /**
265 | * Makes the child a candidate for dragging. Must be an existing child of this layout.
266 | */
267 | public void setViewDraggable(View child, View dragHandle) {
268 | if (null == child || null == dragHandle) {
269 | throw new IllegalArgumentException(
270 | "Draggable children and their drag handles must not be null.");
271 | }
272 |
273 | if (this == child.getParent()) {
274 | dragHandle.setOnTouchListener(new DragHandleOnTouchListener(child));
275 | draggableChildren.put(indexOfChild(child), new DraggableChild());
276 | } else {
277 | Log.e(LOG_TAG, child + " is not a child, cannot make draggable.");
278 | }
279 | }
280 |
281 | /**
282 | * Calls {@link #removeView(android.view.View)} and correctly updates the drag-ability state of
283 | * all remaining views.
284 | */
285 | @SuppressWarnings("UnusedDeclaration")
286 | public void removeDragView(View child) {
287 | if (this == child.getParent()) {
288 | final int index = indexOfChild(child);
289 | removeView(child);
290 |
291 | // update drag-able children mappings
292 | final int mappings = draggableChildren.size();
293 | for (int i = 0; i < mappings; i++) {
294 | final int key = draggableChildren.keyAt(i);
295 | if (key >= index) {
296 | DraggableChild next = draggableChildren.get(key + 1);
297 | if (null == next) {
298 | draggableChildren.delete(key);
299 | } else {
300 | draggableChildren.put(key, next);
301 | }
302 | }
303 | }
304 | }
305 | }
306 |
307 | @Override
308 | public void removeAllViews() {
309 | super.removeAllViews();
310 | draggableChildren.clear();
311 | }
312 |
313 | /**
314 | * If this layout is within a {@link android.widget.ScrollView}, register it here so that it
315 | * can be scrolled during item drags.
316 | */
317 | public void setContainerScrollView(ScrollView scrollView) {
318 | this.containerScrollView = scrollView;
319 | }
320 |
321 | /**
322 | * Sets the height from upper / lower edge at which a container {@link android.widget.ScrollView},
323 | * if one is registered via {@link #setContainerScrollView(android.widget.ScrollView)},
324 | * is scrolled.
325 | */
326 | @SuppressWarnings("UnusedDeclaration")
327 | public void setScrollSensitiveHeight(int height) {
328 | this.scrollSensitiveAreaHeight = height;
329 | }
330 |
331 | @SuppressWarnings("UnusedDeclaration")
332 | public int getScrollSensitiveHeight() {
333 | return scrollSensitiveAreaHeight;
334 | }
335 |
336 | /**
337 | * See {@link com.jmedeisis.draglinearlayout.DragLinearLayout.OnViewSwapListener}.
338 | */
339 | public void setOnViewSwapListener(OnViewSwapListener swapListener) {
340 | this.swapListener = swapListener;
341 | }
342 |
343 | /**
344 | * A linear relationship b/w distance and duration, bounded.
345 | */
346 | private long getTranslateAnimationDuration(float distance) {
347 | return Math.min(MAX_SWITCH_DURATION, Math.max(MIN_SWITCH_DURATION,
348 | (long) (NOMINAL_SWITCH_DURATION * Math.abs(distance) / nominalDistanceScaled)));
349 | }
350 |
351 | /**
352 | * Initiates a new {@link #draggedItem} unless the current one is still
353 | * {@link com.jmedeisis.draglinearlayout.DragLinearLayout.DragItem#detecting}.
354 | */
355 | private void startDetectingDrag(View child) {
356 | if (draggedItem.detecting)
357 | return; // existing drag in process, only one at a time is allowed
358 |
359 | final int position = indexOfChild(child);
360 |
361 | // complete any existing animations, both for the newly selected child and the previous dragged one
362 | draggableChildren.get(position).endExistingAnimation();
363 |
364 | draggedItem.startDetectingOnPossibleDrag(child, position);
365 | if (containerScrollView != null) {
366 | containerScrollView.requestDisallowInterceptTouchEvent(true);
367 | }
368 | }
369 |
370 | private void startDrag() {
371 | // remove layout transition, it conflicts with drag animation
372 | // we will restore it after drag animation end, see onDragStop()
373 | layoutTransition = getLayoutTransition();
374 | if (layoutTransition != null) {
375 | setLayoutTransition(null);
376 | }
377 |
378 | draggedItem.onDragStart();
379 | requestDisallowInterceptTouchEvent(true);
380 | }
381 |
382 | /**
383 | * Animates the dragged item to its final resting position.
384 | */
385 | private void onDragStop() {
386 | draggedItem.settleAnimation = ValueAnimator.ofFloat(draggedItem.totalDragOffset,
387 | draggedItem.totalDragOffset - draggedItem.targetTopOffset)
388 | .setDuration(getTranslateAnimationDuration(draggedItem.targetTopOffset));
389 | draggedItem.settleAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
390 | @Override
391 | public void onAnimationUpdate(ValueAnimator animation) {
392 | if (!draggedItem.detecting) return; // already stopped
393 |
394 | draggedItem.setTotalOffset(((Float) animation.getAnimatedValue()).intValue());
395 |
396 | final int shadowAlpha = (int) ((1 - animation.getAnimatedFraction()) * 255);
397 | if (null != dragTopShadowDrawable) dragTopShadowDrawable.setAlpha(shadowAlpha);
398 | dragBottomShadowDrawable.setAlpha(shadowAlpha);
399 | invalidate();
400 | }
401 | });
402 | draggedItem.settleAnimation.addListener(new AnimatorListenerAdapter() {
403 | @Override
404 | public void onAnimationStart(Animator animation) {
405 | draggedItem.onDragStop();
406 | }
407 |
408 | @Override
409 | public void onAnimationEnd(Animator animation) {
410 | if (!draggedItem.detecting) {
411 | return; // already stopped
412 | }
413 |
414 | draggedItem.settleAnimation = null;
415 | draggedItem.stopDetecting();
416 |
417 | if (null != dragTopShadowDrawable) dragTopShadowDrawable.setAlpha(255);
418 | dragBottomShadowDrawable.setAlpha(255);
419 |
420 | // restore layout transition
421 | if (layoutTransition != null && getLayoutTransition() == null) {
422 | setLayoutTransition(layoutTransition);
423 | }
424 | }
425 | });
426 | draggedItem.settleAnimation.start();
427 | }
428 |
429 | /**
430 | * Updates the dragged item with the given total offset from its starting position.
431 | * Evaluates and executes draggable view swaps.
432 | */
433 | private void onDrag(final int offset) {
434 | draggedItem.setTotalOffset(offset);
435 | invalidate();
436 |
437 | int currentTop = draggedItem.startTop + draggedItem.totalDragOffset;
438 |
439 | handleContainerScroll(currentTop);
440 |
441 | int belowPosition = nextDraggablePosition(draggedItem.position);
442 | int abovePosition = previousDraggablePosition(draggedItem.position);
443 |
444 | View belowView = getChildAt(belowPosition);
445 | View aboveView = getChildAt(abovePosition);
446 |
447 | final boolean isBelow = (belowView != null) &&
448 | (currentTop + draggedItem.height > belowView.getTop() + belowView.getHeight() / 2);
449 | final boolean isAbove = (aboveView != null) &&
450 | (currentTop < aboveView.getTop() + aboveView.getHeight() / 2);
451 |
452 | if (isBelow || isAbove) {
453 | final View switchView = isBelow ? belowView : aboveView;
454 |
455 | // swap elements
456 | final int originalPosition = draggedItem.position;
457 | final int switchPosition = isBelow ? belowPosition : abovePosition;
458 |
459 | draggableChildren.get(switchPosition).cancelExistingAnimation();
460 | final float switchViewStartY = switchView.getY();
461 |
462 | if (null != swapListener) {
463 | swapListener.onSwap(draggedItem.view, draggedItem.position, switchView, switchPosition);
464 | }
465 |
466 | if (isBelow) {
467 | removeViewAt(originalPosition);
468 | removeViewAt(switchPosition - 1);
469 |
470 | addView(belowView, originalPosition);
471 | addView(draggedItem.view, switchPosition);
472 | } else {
473 | removeViewAt(switchPosition);
474 | removeViewAt(originalPosition - 1);
475 |
476 | addView(draggedItem.view, switchPosition);
477 | addView(aboveView, originalPosition);
478 | }
479 | draggedItem.position = switchPosition;
480 |
481 | final ViewTreeObserver switchViewObserver = switchView.getViewTreeObserver();
482 | switchViewObserver.addOnPreDrawListener(new OnPreDrawListener() {
483 | @Override
484 | public boolean onPreDraw() {
485 | switchViewObserver.removeOnPreDrawListener(this);
486 |
487 | final ObjectAnimator switchAnimator = ObjectAnimator.ofFloat(switchView, "y",
488 | switchViewStartY, switchView.getTop())
489 | .setDuration(getTranslateAnimationDuration(switchView.getTop() - switchViewStartY));
490 | switchAnimator.addListener(new AnimatorListenerAdapter() {
491 | @Override
492 | public void onAnimationStart(Animator animation) {
493 | draggableChildren.get(originalPosition).swapAnimation = switchAnimator;
494 | }
495 |
496 | @Override
497 | public void onAnimationEnd(Animator animation) {
498 | draggableChildren.get(originalPosition).swapAnimation = null;
499 | }
500 | });
501 | switchAnimator.start();
502 |
503 | return true;
504 | }
505 | });
506 |
507 | final ViewTreeObserver observer = draggedItem.view.getViewTreeObserver();
508 | observer.addOnPreDrawListener(new OnPreDrawListener() {
509 | @Override
510 | public boolean onPreDraw() {
511 | observer.removeOnPreDrawListener(this);
512 | draggedItem.updateTargetTop();
513 |
514 | // TODO test if still necessary..
515 | // because draggedItem#view#getTop() is only up-to-date NOW
516 | // (and not right after the #addView() swaps above)
517 | // we may need to update an ongoing settle animation
518 | if (draggedItem.settling()) {
519 | Log.d(LOG_TAG, "Updating settle animation");
520 | draggedItem.settleAnimation.removeAllListeners();
521 | draggedItem.settleAnimation.cancel();
522 | onDragStop();
523 | }
524 | return true;
525 | }
526 | });
527 | }
528 | }
529 |
530 | private int previousDraggablePosition(int position) {
531 | int startIndex = draggableChildren.indexOfKey(position);
532 | if (startIndex < 1 || startIndex > draggableChildren.size()) return -1;
533 | return draggableChildren.keyAt(startIndex - 1);
534 | }
535 |
536 | private int nextDraggablePosition(int position) {
537 | int startIndex = draggableChildren.indexOfKey(position);
538 | if (startIndex < -1 || startIndex > draggableChildren.size() - 2) return -1;
539 | return draggableChildren.keyAt(startIndex + 1);
540 | }
541 |
542 | private Runnable dragUpdater;
543 |
544 | private void handleContainerScroll(final int currentTop) {
545 | if (null != containerScrollView) {
546 | final int startScrollY = containerScrollView.getScrollY();
547 | final int absTop = getTop() - startScrollY + currentTop;
548 | final int height = containerScrollView.getHeight();
549 |
550 | final int delta;
551 |
552 | if (absTop < scrollSensitiveAreaHeight) {
553 | delta = (int) (-MAX_DRAG_SCROLL_SPEED * smootherStep(scrollSensitiveAreaHeight, 0, absTop));
554 | } else if (absTop > height - scrollSensitiveAreaHeight) {
555 | delta = (int) (MAX_DRAG_SCROLL_SPEED * smootherStep(height - scrollSensitiveAreaHeight, height, absTop));
556 | } else {
557 | delta = 0;
558 | }
559 |
560 | containerScrollView.removeCallbacks(dragUpdater);
561 | containerScrollView.smoothScrollBy(0, delta);
562 | dragUpdater = new Runnable() {
563 | @Override
564 | public void run() {
565 | if (draggedItem.dragging && startScrollY != containerScrollView.getScrollY()) {
566 | onDrag(draggedItem.totalDragOffset + delta);
567 | }
568 | }
569 | };
570 | containerScrollView.post(dragUpdater);
571 | }
572 | }
573 |
574 | /**
575 | * By Ken Perlin. See Smoothstep - Wikipedia .
576 | */
577 | private static float smootherStep(float edge1, float edge2, float val) {
578 | val = Math.max(0, Math.min((val - edge1) / (edge2 - edge1), 1));
579 | return val * val * val * (val * (val * 6 - 15) + 10);
580 | }
581 |
582 | @Override
583 | protected void dispatchDraw(@NonNull Canvas canvas) {
584 | super.dispatchDraw(canvas);
585 |
586 | if (draggedItem.detecting && (draggedItem.dragging || draggedItem.settling())) {
587 | canvas.save();
588 | canvas.translate(0, draggedItem.totalDragOffset);
589 | draggedItem.viewDrawable.draw(canvas);
590 |
591 | final int left = draggedItem.viewDrawable.getBounds().left;
592 | final int right = draggedItem.viewDrawable.getBounds().right;
593 | final int top = draggedItem.viewDrawable.getBounds().top;
594 | final int bottom = draggedItem.viewDrawable.getBounds().bottom;
595 |
596 | dragBottomShadowDrawable.setBounds(left, bottom, right, bottom + dragShadowHeight);
597 | dragBottomShadowDrawable.draw(canvas);
598 |
599 | if (null != dragTopShadowDrawable) {
600 | dragTopShadowDrawable.setBounds(left, top - dragShadowHeight, right, top);
601 | dragTopShadowDrawable.draw(canvas);
602 | }
603 |
604 | canvas.restore();
605 | }
606 | }
607 |
608 | /*
609 | * Note regarding touch handling:
610 | * In general, we have three cases -
611 | * 1) User taps outside any children.
612 | * #onInterceptTouchEvent receives DOWN
613 | * #onTouchEvent receives DOWN
614 | * draggedItem.detecting == false, we return false and no further events are received
615 | * 2) User taps on non-interactive drag handle / child, e.g. TextView or ImageView.
616 | * #onInterceptTouchEvent receives DOWN
617 | * DragHandleOnTouchListener (attached to each draggable child) #onTouch receives DOWN
618 | * #startDetectingDrag is called, draggedItem is now detecting
619 | * view does not handle touch, so our #onTouchEvent receives DOWN
620 | * draggedItem.detecting == true, we #startDrag() and proceed to handle the drag
621 | * 3) User taps on interactive drag handle / child, e.g. Button.
622 | * #onInterceptTouchEvent receives DOWN
623 | * DragHandleOnTouchListener (attached to each draggable child) #onTouch receives DOWN
624 | * #startDetectingDrag is called, draggedItem is now detecting
625 | * view handles touch, so our #onTouchEvent is not called yet
626 | * #onInterceptTouchEvent receives ACTION_MOVE
627 | * if dy > touch slop, we assume user wants to drag and intercept the event
628 | * #onTouchEvent receives further ACTION_MOVE events, proceed to handle the drag
629 | *
630 | * For cases 2) and 3), lifting the active pointer at any point in the sequence of events
631 | * triggers #onTouchEnd and the draggedItem, if detecting, is #stopDetecting.
632 | */
633 |
634 | @Override
635 | public boolean onInterceptTouchEvent(MotionEvent event) {
636 | switch (MotionEventCompat.getActionMasked(event)) {
637 | case MotionEvent.ACTION_DOWN: {
638 | if (draggedItem.detecting) return false; // an existing item is (likely) settling
639 | downY = (int) MotionEventCompat.getY(event, 0);
640 | activePointerId = MotionEventCompat.getPointerId(event, 0);
641 | break;
642 | }
643 | case MotionEvent.ACTION_MOVE: {
644 | if (!draggedItem.detecting) return false;
645 | if (INVALID_POINTER_ID == activePointerId) break;
646 | final int pointerIndex = event.findPointerIndex(activePointerId);
647 | final float y = MotionEventCompat.getY(event, pointerIndex);
648 | final float dy = y - downY;
649 | if (Math.abs(dy) > slop) {
650 | startDrag();
651 | return true;
652 | }
653 | return false;
654 | }
655 | case MotionEvent.ACTION_POINTER_UP: {
656 | final int pointerIndex = MotionEventCompat.getActionIndex(event);
657 | final int pointerId = MotionEventCompat.getPointerId(event, pointerIndex);
658 |
659 | if (pointerId != activePointerId)
660 | break; // if active pointer, fall through and cancel!
661 | }
662 | case MotionEvent.ACTION_CANCEL:
663 | case MotionEvent.ACTION_UP: {
664 | onTouchEnd();
665 |
666 | if (draggedItem.detecting) draggedItem.stopDetecting();
667 | break;
668 | }
669 | }
670 |
671 | return false;
672 | }
673 |
674 | @Override
675 | public boolean onTouchEvent(@NonNull MotionEvent event) {
676 | switch (MotionEventCompat.getActionMasked(event)) {
677 | case MotionEvent.ACTION_DOWN: {
678 | if (!draggedItem.detecting || draggedItem.settling()) return false;
679 | startDrag();
680 | return true;
681 | }
682 | case MotionEvent.ACTION_MOVE: {
683 | if (!draggedItem.dragging) break;
684 | if (INVALID_POINTER_ID == activePointerId) break;
685 |
686 | int pointerIndex = event.findPointerIndex(activePointerId);
687 | int lastEventY = (int) MotionEventCompat.getY(event, pointerIndex);
688 | int deltaY = lastEventY - downY;
689 |
690 | onDrag(deltaY);
691 | return true;
692 | }
693 | case MotionEvent.ACTION_POINTER_UP: {
694 | final int pointerIndex = MotionEventCompat.getActionIndex(event);
695 | final int pointerId = MotionEventCompat.getPointerId(event, pointerIndex);
696 |
697 | if (pointerId != activePointerId)
698 | break; // if active pointer, fall through and cancel!
699 | }
700 | case MotionEvent.ACTION_CANCEL:
701 | case MotionEvent.ACTION_UP: {
702 | onTouchEnd();
703 |
704 | if (draggedItem.dragging) {
705 | onDragStop();
706 | } else if (draggedItem.detecting) {
707 | draggedItem.stopDetecting();
708 | }
709 | return true;
710 | }
711 | }
712 | return false;
713 | }
714 |
715 | private void onTouchEnd() {
716 | downY = -1;
717 | activePointerId = INVALID_POINTER_ID;
718 | }
719 |
720 | private class DragHandleOnTouchListener implements OnTouchListener {
721 | private final View view;
722 |
723 | public DragHandleOnTouchListener(final View view) {
724 | this.view = view;
725 | }
726 |
727 | @Override
728 | public boolean onTouch(View v, MotionEvent event) {
729 | if (MotionEvent.ACTION_DOWN == MotionEventCompat.getActionMasked(event)) {
730 | startDetectingDrag(view);
731 | }
732 | return false;
733 | }
734 | }
735 |
736 | private BitmapDrawable getDragDrawable(View view) {
737 | int top = view.getTop();
738 | int left = view.getLeft();
739 |
740 | Bitmap bitmap = getBitmapFromView(view);
741 |
742 | BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
743 |
744 | drawable.setBounds(new Rect(left, top, left + view.getWidth(), top + view.getHeight()));
745 |
746 | return drawable;
747 | }
748 |
749 | /**
750 | * @return a bitmap showing a screenshot of the view passed in.
751 | */
752 | private static Bitmap getBitmapFromView(View view) {
753 | Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
754 | Canvas canvas = new Canvas(bitmap);
755 | view.draw(canvas);
756 | return bitmap;
757 | }
758 | }
759 |
--------------------------------------------------------------------------------