├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── RecyclerViewSwipeDismiss.gif
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── github
│ │ └── codefalling
│ │ └── recyclerviewswipedismiss
│ │ └── sample
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── io
│ │ └── github
│ │ └── codefalling
│ │ └── recyclerviewswipedismiss
│ │ └── sample
│ │ └── MainActivity.java
│ └── res
│ ├── layout
│ └── activity_main.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── github
│ │ └── codefalling
│ │ └── recyclerviewswipedismiss
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── io
│ │ └── github
│ │ └── codefalling
│ │ └── recyclerviewswipedismiss
│ │ └── SwipeDismissRecyclerViewTouchListener.java
│ └── res
│ └── values
│ └── strings.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | .DS_Store
6 | /build
7 | /captures
8 | #built application files
9 | *.apk
10 | *.ap_
11 |
12 | # files for the dex VM
13 | *.dex
14 |
15 | # Java class files
16 | *.class
17 |
18 | # generated files
19 | bin/
20 | gen/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Windows thumbnail db
26 | Thumbs.db
27 |
28 | # OSX files
29 | .DS_Store
30 |
31 | # Eclipse project files
32 | .classpath
33 | .project
34 |
35 | # Android Studio
36 | *.iml
37 | .idea
38 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
39 | .gradle
40 | build/
41 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk: openjdk7
3 | android:
4 | components:
5 | - build-tools-21.1.2
6 | - extra-android-support
7 | - extra-android-m2repository
8 | - android-21
9 | - sys-img-armeabi-v7a-android-21
10 |
11 | licenses:
12 | - '.+'
13 | notifications:
14 | email: false
15 | script: ./gradlew build
16 |
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, SheJinxin(code.falling@gmail.com)
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](http://android-arsenal.com/details/1/1838)
4 | 
5 | [](https://jitpack.io/#CodeFalling/RecyclerViewSwipeDismiss/)
6 | [](https://travis-ci.org/CodeFalling/RecyclerViewSwipeDismiss)
7 | # RecyclerViewSwipeDismiss
8 | A very easy-to-use and non-intrusive implement of Swipe to dismiss for RecyclerView.
9 |
10 | ## Preview
11 |
12 | 
13 |
14 |
15 | ## How to use
16 |
17 | - Add these lines to your `build.gradle`
18 |
19 | ```gradle
20 | repositories {
21 | maven {
22 | url "https://jitpack.io"
23 | }
24 | }
25 |
26 | dependencies {
27 | compile 'com.github.CodeFalling:RecyclerViewSwipeDismiss:v1.1.3'
28 | }
29 | ```
30 |
31 | - Build `onTouchListener` and bind it to your `RecyclerView`
32 |
33 | ```java
34 |
35 | SwipeDismissRecyclerViewTouchListener listener = new SwipeDismissRecyclerViewTouchListener.Builder(
36 | recyclerView,
37 | new SwipeDismissRecyclerViewTouchListener.DismissCallbacks() {
38 | @Override
39 | public boolean canDismiss(int position) {
40 | return true;
41 | }
42 |
43 | @Override
44 | public void onDismiss(View view) {
45 | // Do what you want when dismiss
46 |
47 | }
48 | })
49 | .setIsVertical(false)
50 | .setItemTouchCallback(
51 | new SwipeDismissRecyclerViewTouchListener.OnItemTouchCallBack() {
52 | @Override
53 | public void onTouch(int index) {
54 | // Do what you want when item be touched
55 | }
56 | })
57 | .setItemClickCallback(new SwipeDismissRecyclerViewTouchListener.OnItemClickCallBack() {
58 | @Override
59 | public void onClick(int position) {
60 | // Do what you want when item be clicked }
61 | })
62 | .setBackgroundId(R.drawable.bg_item_normal, R.drawable.bg_item_selected)
63 | .create();
64 | recyclerView.setOnTouchListener(listener);
65 | ```
66 |
67 | ## More
68 |
69 | - `setIsVertical(false)` means allow **swipe in horizontal direction**
70 |
71 | - `listener.setEnabled(false)` can disable swipe to dismiss
72 |
73 | - `onTouch` will be called when MOUSE_UP on item without swipe
74 |
75 | - `onClick` will be called when ACTION_UP on item within 1 second and move no more than a fixed distance
76 |
77 | - By use `setBackgroundId`, you can set background id for item's normal and pressed state, just like the normal effect in RecyclerView
78 |
79 | ## Sample
80 |
81 | You can see sample code in [`sample/MainActivity.java`](https://github.com/CodeFalling/RecyclerViewSwipeDismiss/blob/master/app%2Fsrc%2Fmain%2Fjava%2Fio%2Fgithub%2Fcodefalling%2Frecyclerviewswipedismiss%2Fsample%2FMainActivity.java)
82 |
--------------------------------------------------------------------------------
/RecyclerViewSwipeDismiss.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xcodebuild/RecyclerViewSwipeDismiss/a659e8496429073374a81a0ddc9860713e55730a/RecyclerViewSwipeDismiss.gif
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | applicationId "io.github.codefalling.recyclerviewswipedismiss.sample"
9 | minSdkVersion 15
10 | targetSdkVersion 21
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 fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:22.1.1'
25 | compile project(':library')
26 | }
27 |
--------------------------------------------------------------------------------
/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 /Users/falling/app/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/src/androidTest/java/io/github/codefalling/recyclerviewswipedismiss/sample/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package io.github.codefalling.recyclerviewswipedismiss.sample;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/codefalling/recyclerviewswipedismiss/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.codefalling.recyclerviewswipedismiss.sample;
2 |
3 | import android.app.AlertDialog;
4 | import android.os.Bundle;
5 | import android.support.v7.app.ActionBarActivity;
6 | import android.support.v7.widget.LinearLayoutManager;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.TextView;
11 | import android.widget.Toast;
12 |
13 | import java.util.LinkedList;
14 | import java.util.List;
15 |
16 | import io.github.codefalling.recyclerviewswipedismiss.SwipeDismissRecyclerViewTouchListener;
17 |
18 |
19 | public class MainActivity extends ActionBarActivity {
20 |
21 | private void showDialog(String msg){
22 | AlertDialog alert = new AlertDialog.Builder(MainActivity.this)
23 | .setTitle("alert")
24 | .setMessage(msg)
25 | .setCancelable(false)
26 | .create();
27 | alert.setCanceledOnTouchOutside(true);
28 | alert.show();
29 | }
30 |
31 | @Override
32 | protected void onCreate(Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | setContentView(R.layout.activity_main);
35 |
36 | final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
37 | final RecyclerView anotherRecyclerView = (RecyclerView) findViewById(R.id.recyclerHorizontalView);
38 |
39 | LinearLayoutManager layoutManager = new LinearLayoutManager(this);
40 | LinearLayoutManager horizontalLayoutManager = new LinearLayoutManager(this);
41 | horizontalLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
42 |
43 | recyclerView.setLayoutManager(layoutManager);
44 | anotherRecyclerView.setLayoutManager(horizontalLayoutManager);
45 |
46 | List dataset = new LinkedList();
47 | for (int i = 0; i < 100; i++){
48 | dataset.add("item" + i);
49 | }
50 | final MyAdapter adapter = new MyAdapter(dataset);
51 |
52 | recyclerView.setAdapter(adapter);
53 | anotherRecyclerView.setAdapter(adapter);
54 |
55 | // Bind touch listener
56 |
57 | SwipeDismissRecyclerViewTouchListener listener = new SwipeDismissRecyclerViewTouchListener.Builder(
58 | recyclerView,
59 | new SwipeDismissRecyclerViewTouchListener.DismissCallbacks() {
60 | @Override
61 | public boolean canDismiss(int position) {
62 | return true;
63 | }
64 |
65 | @Override
66 | public void onDismiss(View view) {
67 | int id = recyclerView.getChildPosition(view);
68 | adapter.mDataset.remove(id);
69 | adapter.notifyDataSetChanged();
70 |
71 | Toast.makeText(getBaseContext(), String.format("Delete item %d",id),Toast.LENGTH_LONG).show();
72 | }
73 | })
74 | .setIsVertical(false)
75 | .setItemTouchCallback(
76 | new SwipeDismissRecyclerViewTouchListener.OnItemTouchCallBack() {
77 | @Override
78 | public void onTouch(int index) {
79 | showDialog(String.format("Click item %d", index));
80 | }
81 | })
82 | .create();
83 |
84 | recyclerView.setOnTouchListener(listener);
85 |
86 |
87 | // set touch listener for recyclerHorizontalView(horizontal swipe to remove)
88 |
89 | SwipeDismissRecyclerViewTouchListener verticalListener = new SwipeDismissRecyclerViewTouchListener.Builder(
90 | anotherRecyclerView,
91 | new SwipeDismissRecyclerViewTouchListener.DismissCallbacks() {
92 | @Override
93 | public boolean canDismiss(int position) {
94 | return true;
95 | }
96 |
97 | @Override
98 | public void onDismiss(View view) {
99 | int id = recyclerView.getChildPosition(view);
100 | adapter.mDataset.remove(id);
101 | adapter.notifyDataSetChanged();
102 |
103 | Toast.makeText(getBaseContext(), String.format("Delete item %d",id),Toast.LENGTH_LONG).show();
104 | }
105 | }).setIsVertical(true).create();
106 |
107 | anotherRecyclerView.setOnTouchListener(verticalListener);
108 |
109 |
110 | }
111 | public class MyAdapter extends RecyclerView.Adapter{
112 |
113 | public List mDataset;
114 |
115 | public MyAdapter(List dataset) {
116 | super();
117 | mDataset = dataset;
118 | }
119 |
120 | @Override
121 | public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
122 | View view = View.inflate(viewGroup.getContext(), android.R.layout.simple_list_item_1, null);
123 | ViewHolder holder = new ViewHolder(view);
124 | return holder;
125 | }
126 |
127 | @Override
128 | public void onBindViewHolder(ViewHolder viewHolder, int i) {
129 | viewHolder.mTextView.setText(mDataset.get(i));
130 | }
131 |
132 | @Override
133 | public int getItemCount() {
134 | return mDataset.size();
135 | }
136 |
137 | public class ViewHolder extends RecyclerView.ViewHolder{
138 |
139 | public TextView mTextView;
140 | public ViewHolder(View itemView) {
141 | super(itemView);
142 | mTextView = (TextView) itemView;
143 | }
144 | }
145 | }
146 |
147 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
15 |
16 |
21 |
22 |
26 |
27 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xcodebuild/RecyclerViewSwipeDismiss/a659e8496429073374a81a0ddc9860713e55730a/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xcodebuild/RecyclerViewSwipeDismiss/a659e8496429073374a81a0ddc9860713e55730a/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xcodebuild/RecyclerViewSwipeDismiss/a659e8496429073374a81a0ddc9860713e55730a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xcodebuild/RecyclerViewSwipeDismiss/a659e8496429073374a81a0ddc9860713e55730a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Sample
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.2.2'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xcodebuild/RecyclerViewSwipeDismiss/a659e8496429073374a81a0ddc9860713e55730a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 21
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 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | compile 'com.android.support:appcompat-v7:22.1.1'
24 | compile 'com.android.support:recyclerview-v7:21.0.3'
25 | }
26 |
--------------------------------------------------------------------------------
/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 /Users/falling/app/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 |
--------------------------------------------------------------------------------
/library/src/androidTest/java/io/github/codefalling/recyclerviewswipedismiss/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package io.github.codefalling.recyclerviewswipedismiss;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/codefalling/recyclerviewswipedismiss/SwipeDismissRecyclerViewTouchListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | * Modified by falling on 15-5-1.
16 | */
17 |
18 | package io.github.codefalling.recyclerviewswipedismiss;
19 |
20 |
21 | import android.animation.Animator;
22 | import android.animation.AnimatorListenerAdapter;
23 | import android.animation.ValueAnimator;
24 | import android.graphics.Rect;
25 | import android.os.SystemClock;
26 | import android.support.v7.widget.RecyclerView;
27 | import android.view.MotionEvent;
28 | import android.view.VelocityTracker;
29 | import android.view.View;
30 | import android.view.ViewConfiguration;
31 | import android.view.ViewGroup;
32 | import android.widget.ListView;
33 |
34 | import java.util.ArrayList;
35 | import java.util.Collections;
36 | import java.util.List;
37 |
38 | public class SwipeDismissRecyclerViewTouchListener implements View.OnTouchListener {
39 | /**
40 | * Max allowed duration for a "click", in milliseconds.
41 | */
42 | private static final int MAX_CLICK_DURATION = 1000;
43 |
44 | // Cached ViewConfiguration and system-wide constant values
45 | private int mSlop;
46 | private int mMinFlingVelocity;
47 | private int mMaxFlingVelocity;
48 | private long mAnimationTime;
49 |
50 | // Fixed properties
51 | private RecyclerView mRecyclerView;
52 | private DismissCallbacks mCallbacks;
53 | private boolean mIsVertical;
54 | private OnItemTouchCallBack mItemTouchCallback;
55 | private OnItemClickCallBack mItemClickCallback;
56 |
57 | private long pressStartTime;
58 | private float pressedX;
59 | private float pressedY;
60 |
61 | private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
62 |
63 | // Transient properties
64 | private List mPendingDismisses = new ArrayList();
65 | private int mDismissAnimationRefCount = 0;
66 | private float mDownX;
67 | private float mDownY;
68 | private boolean mSwiping;
69 | private int mSwipingSlop;
70 | private VelocityTracker mVelocityTracker;
71 | private int mDownPosition;
72 | private View mDownView;
73 | private boolean mPaused;
74 | private int mBackgroundPressId;
75 | private int mBackgroundNormalId;
76 |
77 | private boolean hasMoveAfterDown;
78 |
79 | public SwipeDismissRecyclerViewTouchListener(Builder builder) {
80 | ViewConfiguration vc = ViewConfiguration.get(builder.mRecyclerView.getContext());
81 | mSlop = vc.getScaledTouchSlop();
82 | mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
83 | mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
84 | mAnimationTime = builder.mRecyclerView.getContext().getResources().getInteger(
85 | android.R.integer.config_shortAnimTime);
86 | mRecyclerView = builder.mRecyclerView;
87 | mCallbacks = builder.mCallbacks;
88 | mIsVertical = builder.mIsVertical;
89 | mItemTouchCallback = builder.mItemTouchCallback;
90 | mItemClickCallback = builder.mItemClickCallback;
91 | mBackgroundNormalId = builder.mBackgroundNormalId;
92 | mBackgroundPressId = builder.mBackgroundPressId;
93 | }
94 |
95 | public void setEnabled(boolean enabled) {
96 | mPaused = !enabled;
97 | }
98 |
99 | @Override
100 | public boolean onTouch(View view, MotionEvent motionEvent) {
101 | if (mViewWidth < 2) {
102 | mViewWidth = mIsVertical ? mRecyclerView.getHeight() : mRecyclerView.getWidth();
103 | }
104 |
105 | switch (motionEvent.getActionMasked()) {
106 | case MotionEvent.ACTION_DOWN: {
107 | hasMoveAfterDown = false;
108 |
109 | pressStartTime = System.currentTimeMillis();
110 | pressedX = motionEvent.getX();
111 | pressedY = motionEvent.getY();
112 |
113 | if (mPaused) {
114 | return false;
115 | }
116 |
117 | // Find the child view that was touched (perform a hit test)
118 | Rect rect = new Rect();
119 | int childCount = mRecyclerView.getChildCount();
120 | int[] listViewCoords = new int[2];
121 | mRecyclerView.getLocationOnScreen(listViewCoords);
122 | int x = (int) motionEvent.getRawX() - listViewCoords[0];
123 | int y = (int) motionEvent.getRawY() - listViewCoords[1];
124 | View child;
125 |
126 | mDownView = mRecyclerView.findChildViewUnder(x, y);
127 | updateItemBackground(mDownView, motionEvent);
128 |
129 | if (mDownView != null) {
130 | mDownX = motionEvent.getRawX();
131 | mDownY = motionEvent.getRawY();
132 |
133 | mDownPosition = mRecyclerView.getChildPosition(mDownView);
134 | if (mCallbacks.canDismiss(mDownPosition)) {
135 | mVelocityTracker = VelocityTracker.obtain();
136 | mVelocityTracker.addMovement(motionEvent);
137 | } else {
138 | mDownView = null;
139 | }
140 | }
141 | return false;
142 | }
143 |
144 | case MotionEvent.ACTION_CANCEL: {
145 | if (mVelocityTracker == null) {
146 | break;
147 | }
148 |
149 | updateItemBackground(mDownView, motionEvent);
150 |
151 | if (mDownView != null && mSwiping) {
152 | // cancel
153 | if (mIsVertical) {
154 | mDownView.animate()
155 | .translationY(0)
156 | .alpha(1)
157 | .setDuration(mAnimationTime)
158 | .setListener(null);
159 | } else {
160 | mDownView.animate()
161 | .translationX(0)
162 | .alpha(1)
163 | .setDuration(mAnimationTime)
164 | .setListener(null);
165 | }
166 | }
167 | mVelocityTracker.recycle();
168 | mVelocityTracker = null;
169 | mDownX = 0;
170 | mDownY = 0;
171 | mDownView = null;
172 | mDownPosition = RecyclerView.NO_POSITION;
173 | mSwiping = false;
174 | break;
175 | }
176 |
177 | case MotionEvent.ACTION_UP: {
178 | long pressDuration = System.currentTimeMillis() - pressStartTime;
179 | if (pressDuration < MAX_CLICK_DURATION && distance(pressedX, pressedY, motionEvent.getX(), motionEvent.getY()) < mSlop) {
180 | mItemClickCallback.onClick(mRecyclerView.getChildPosition(mDownView));
181 | return true;
182 | }
183 |
184 | updateItemBackground(mDownView, motionEvent);
185 |
186 | if (!mSwiping && mDownView != null && mItemTouchCallback != null) {
187 | mItemTouchCallback.onTouch(mRecyclerView.getChildPosition(mDownView));
188 | mVelocityTracker.recycle();
189 | mVelocityTracker = null;
190 | mDownX = 0;
191 | mDownY = 0;
192 | mDownView = null;
193 | mDownPosition = ListView.INVALID_POSITION;
194 | mSwiping = false;
195 | return true;
196 | }
197 |
198 | if (mVelocityTracker == null) {
199 | break;
200 | }
201 |
202 | float deltaX = motionEvent.getRawX() - mDownX;
203 | float deltaY = motionEvent.getRawY() - mDownY;
204 | mVelocityTracker.addMovement(motionEvent);
205 | mVelocityTracker.computeCurrentVelocity(1000);
206 | float velocityX = mVelocityTracker.getXVelocity();
207 | float velocityY = mVelocityTracker.getYVelocity();
208 | float absVelocityX = Math.abs(velocityX);
209 | float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
210 | boolean dismiss = false;
211 | boolean dismissRight = false;
212 |
213 | if (mIsVertical) {
214 | if (Math.abs(deltaY) > mViewWidth / 2 && mSwiping) {
215 | dismiss = true;
216 | dismissRight = deltaY > 0;
217 | } else if (mMinFlingVelocity <= absVelocityY && absVelocityY <= mMaxFlingVelocity
218 | && absVelocityX < absVelocityY && mSwiping) {
219 | // dismiss only if flinging in the same direction as dragging
220 | dismiss = (velocityY < 0) == (deltaY < 0);
221 | dismissRight = mVelocityTracker.getYVelocity() > 0;
222 | }
223 | if (dismiss && mDownPosition != ListView.INVALID_POSITION) {
224 | // dismiss
225 | final View downView = mDownView; // mDownView gets null'd before animation ends
226 | final int downPosition = mDownPosition;
227 | ++mDismissAnimationRefCount;
228 | mDownView.animate()
229 | .translationY(dismissRight ? mViewWidth : -mViewWidth)
230 | .alpha(0)
231 | .setDuration(mAnimationTime)
232 | .setListener(new AnimatorListenerAdapter() {
233 | @Override
234 | public void onAnimationEnd(Animator animation) {
235 | performDismiss(downView, downPosition);
236 | }
237 | });
238 | } else {
239 | // cancel
240 | mDownView.animate()
241 | .translationY(0)
242 | .alpha(1)
243 | .setDuration(mAnimationTime)
244 | .setListener(null);
245 | }
246 | mVelocityTracker.recycle();
247 | mVelocityTracker = null;
248 | mDownX = 0;
249 | mDownY = 0;
250 | mDownView = null;
251 | mDownPosition = ListView.INVALID_POSITION;
252 | mSwiping = false;
253 | } else {
254 |
255 | if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) {
256 | dismiss = true;
257 | dismissRight = deltaX > 0;
258 | } else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
259 | && absVelocityY < absVelocityX && mSwiping) {
260 | // dismiss only if flinging in the same direction as dragging
261 | dismiss = (velocityX < 0) == (deltaX < 0);
262 | dismissRight = mVelocityTracker.getXVelocity() > 0;
263 | }
264 | if (dismiss && mDownPosition != ListView.INVALID_POSITION) {
265 | // dismiss
266 | final View downView = mDownView; // mDownView gets null'd before animation ends
267 | final int downPosition = mDownPosition;
268 | ++mDismissAnimationRefCount;
269 | mDownView.animate()
270 | .translationX(dismissRight ? mViewWidth : -mViewWidth)
271 | .alpha(0)
272 | .setDuration(mAnimationTime)
273 | .setListener(new AnimatorListenerAdapter() {
274 | @Override
275 | public void onAnimationEnd(Animator animation) {
276 | performDismiss(downView, downPosition);
277 | }
278 | });
279 | } else {
280 | // cancel
281 | mDownView.animate()
282 | .translationX(0)
283 | .alpha(1)
284 | .setDuration(mAnimationTime)
285 | .setListener(null);
286 | }
287 | mVelocityTracker.recycle();
288 | mVelocityTracker = null;
289 | mDownX = 0;
290 | mDownY = 0;
291 | mDownView = null;
292 | mDownPosition = ListView.INVALID_POSITION;
293 | mSwiping = false;
294 | }
295 | break;
296 | }
297 |
298 | case MotionEvent.ACTION_MOVE: {
299 | hasMoveAfterDown = true;
300 |
301 | if (mVelocityTracker == null || mPaused) {
302 | break;
303 | }
304 |
305 | updateItemBackground(mDownView, motionEvent);
306 |
307 | mVelocityTracker.addMovement(motionEvent);
308 | float deltaX = motionEvent.getRawX() - mDownX;
309 | float deltaY = motionEvent.getRawY() - mDownY;
310 | if (mIsVertical) {
311 | if ((Math.abs(deltaX) >= Math.abs(deltaY) / 2) && mBackgroundNormalId != 0) {
312 | mDownView.setBackgroundResource(mBackgroundNormalId);
313 | }
314 |
315 | if (Math.abs(deltaY) > mSlop && Math.abs(deltaX) < Math.abs(deltaY) / 2) {
316 | mSwiping = true;
317 | mSwipingSlop = (deltaY > 0 ? mSlop : -mSlop);
318 | mRecyclerView.requestDisallowInterceptTouchEvent(true);
319 |
320 | // Cancel ListView's touch (un-highlighting the item)
321 | MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
322 | cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
323 | (motionEvent.getActionIndex()
324 | << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
325 | mRecyclerView.onTouchEvent(cancelEvent);
326 | cancelEvent.recycle();
327 | }
328 |
329 | if (mSwiping) {
330 | mDownView.setTranslationY(deltaY);
331 | mDownView.setAlpha(Math.max(0f, Math.min(1f,
332 | 1f - 2f * Math.abs(deltaY) / mViewWidth)));
333 | return true;
334 | }
335 |
336 | } else {
337 | if ((Math.abs(deltaY) >= Math.abs(deltaX) / 2) && mBackgroundNormalId != 0) {
338 | mDownView.setBackgroundResource(mBackgroundNormalId);
339 | }
340 |
341 | if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
342 | mSwiping = true;
343 | mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
344 | mRecyclerView.requestDisallowInterceptTouchEvent(true);
345 |
346 | // Cancel ListView's touch (un-highlighting the item)
347 | MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
348 | cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
349 | (motionEvent.getActionIndex()
350 | << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
351 | mRecyclerView.onTouchEvent(cancelEvent);
352 | cancelEvent.recycle();
353 | }
354 |
355 | if (mSwiping) {
356 | mDownView.setTranslationX(deltaX);
357 | mDownView.setAlpha(Math.max(0f, Math.min(1f,
358 | 1f - 2f * Math.abs(deltaX) / mViewWidth)));
359 | return true;
360 | }
361 | }
362 | break;
363 | }
364 | }
365 | return false;
366 | }
367 |
368 | private void performDismiss(final View dismissView, final int dismissPosition) {
369 | // Animate the dismissed list item to zero-height and fire the dismiss callback when
370 | // all dismissed list item animations have completed. This triggers layout on each animation
371 | // frame; in the future we may want to do something smarter and more performant.
372 |
373 | final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
374 | final int originalHeight;
375 | if (mIsVertical)
376 | originalHeight = dismissView.getWidth();
377 | else
378 | originalHeight = dismissView.getHeight();
379 |
380 | ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
381 |
382 | animator.addListener(new AnimatorListenerAdapter() {
383 | @Override
384 | public void onAnimationEnd(Animator animation) {
385 | --mDismissAnimationRefCount;
386 | if (mDismissAnimationRefCount == 0) {
387 | // No active animations, process all pending dismisses.
388 | // Sort by descending position
389 | Collections.sort(mPendingDismisses);
390 |
391 | int[] dismissPositions = new int[mPendingDismisses.size()];
392 | for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
393 | dismissPositions[i] = mPendingDismisses.get(i).position;
394 | }
395 | mCallbacks.onDismiss(dismissView);
396 |
397 | // Reset mDownPosition to avoid MotionEvent.ACTION_UP trying to start a dismiss
398 | // animation with a stale position
399 | mDownPosition = ListView.INVALID_POSITION;
400 |
401 | ViewGroup.LayoutParams lp;
402 | for (PendingDismissData pendingDismiss : mPendingDismisses) {
403 | // Reset view presentation
404 | pendingDismiss.view.setAlpha(1f);
405 | if (mIsVertical)
406 | pendingDismiss.view.setTranslationY(0);
407 | else
408 | pendingDismiss.view.setTranslationX(0);
409 | lp = pendingDismiss.view.getLayoutParams();
410 | if (mIsVertical)
411 | lp.width = originalHeight;
412 | else
413 | lp.height = originalHeight;
414 |
415 | pendingDismiss.view.setLayoutParams(lp);
416 | }
417 |
418 | // Send a cancel event
419 | long time = SystemClock.uptimeMillis();
420 | MotionEvent cancelEvent = MotionEvent.obtain(time, time,
421 | MotionEvent.ACTION_CANCEL, 0, 0, 0);
422 | mRecyclerView.dispatchTouchEvent(cancelEvent);
423 |
424 | mPendingDismisses.clear();
425 | }
426 | }
427 | });
428 |
429 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
430 | @Override
431 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
432 | if (mIsVertical)
433 | lp.width = (Integer) valueAnimator.getAnimatedValue();
434 | else
435 | lp.height = (Integer) valueAnimator.getAnimatedValue();
436 | dismissView.setLayoutParams(lp);
437 | }
438 | });
439 |
440 | mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
441 | animator.start();
442 | }
443 |
444 | public interface DismissCallbacks {
445 | boolean canDismiss(int position);
446 |
447 | void onDismiss(View view);
448 | }
449 |
450 | public interface OnItemTouchCallBack {
451 | void onTouch(int position);
452 | }
453 |
454 | public interface OnItemClickCallBack {
455 | void onClick(int position);
456 | }
457 |
458 | static public class Builder {
459 | private RecyclerView mRecyclerView;
460 | private DismissCallbacks mCallbacks;
461 |
462 | private OnItemTouchCallBack mItemTouchCallback = null;
463 | private OnItemClickCallBack mItemClickCallback = null;
464 | private boolean mIsVertical = false;
465 | private int mBackgroundPressId;
466 | private int mBackgroundNormalId;
467 |
468 | public Builder(RecyclerView recyclerView, DismissCallbacks callbacks) {
469 | mRecyclerView = recyclerView;
470 | mCallbacks = callbacks;
471 | }
472 |
473 | public Builder setIsVertical(boolean isVertical) {
474 | mIsVertical = isVertical;
475 | return this;
476 | }
477 |
478 | public Builder setItemTouchCallback(OnItemTouchCallBack callBack) {
479 | mItemTouchCallback = callBack;
480 | return this;
481 | }
482 |
483 | public Builder setItemClickCallback(OnItemClickCallBack callBack) {
484 | mItemClickCallback = callBack;
485 | return this;
486 | }
487 |
488 | public Builder setBackgroundId(int backgroundNormalId, int backgroundPressId) {
489 | mBackgroundNormalId = backgroundNormalId;
490 | mBackgroundPressId = backgroundPressId;
491 | return this;
492 | }
493 |
494 | public SwipeDismissRecyclerViewTouchListener create() {
495 | return new SwipeDismissRecyclerViewTouchListener(this);
496 | }
497 |
498 |
499 | }
500 |
501 | class PendingDismissData implements Comparable {
502 | public int position;
503 | public View view;
504 |
505 | public PendingDismissData(int position, View view) {
506 | this.position = position;
507 | this.view = view;
508 | }
509 |
510 | @Override
511 | public int compareTo(PendingDismissData other) {
512 | // Sort by descending position
513 | return other.position - position;
514 | }
515 | }
516 |
517 | private float distance(float x1, float y1, float x2, float y2) {
518 | float dx = x1 - x2;
519 | float dy = y1 - y2;
520 | return (float) Math.sqrt(dx * dx + dy * dy);
521 | }
522 |
523 | private void updateItemBackground(final View mDownView, MotionEvent motionEvent) {
524 | if (mBackgroundPressId == 0 || mBackgroundNormalId == 0 || mDownView == null) {
525 | return;
526 | }
527 |
528 | switch (motionEvent.getActionMasked()) {
529 | case MotionEvent.ACTION_DOWN:
530 | mRecyclerView.postDelayed(new Runnable() {
531 | @Override
532 | public void run() {
533 | if (!hasMoveAfterDown) {
534 | mDownView.setBackgroundResource(mBackgroundPressId);
535 | }
536 | }
537 | }, ViewConfiguration.getTapTimeout());
538 | break;
539 | case MotionEvent.ACTION_UP:
540 | case MotionEvent.ACTION_CANCEL:
541 | mDownView.setBackgroundResource(mBackgroundNormalId);
542 | break;
543 | default:
544 | break;
545 | }
546 | }
547 |
548 | }
549 |
550 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':library'
2 | include ':app'
--------------------------------------------------------------------------------