├── .gitignore
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── recyclerviewenhanced
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── nikhilpanju
│ │ └── recyclerviewenhanced
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── nikhilpanju
│ │ │ └── recyclerviewenhanced
│ │ │ ├── OnActivityTouchListener.java
│ │ │ └── RecyclerTouchListener.java
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── nikhilpanju
│ └── recyclerviewenhanced
│ └── ExampleUnitTest.java
├── sample
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── nikhilpanju
│ │ └── recyclerviewsample
│ │ └── ApplicationTest.java
│ ├── common
│ └── images
│ │ └── Demo.gif
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── nikhilpanju
│ │ │ └── recyclerviewsample
│ │ │ ├── MainActivity.java
│ │ │ ├── RowModel.java
│ │ │ └── ToastUtil.java
│ └── res
│ │ ├── drawable
│ │ ├── ic_add_black_24dp.xml
│ │ ├── ic_build_black_24dp.xml
│ │ └── ic_mode_edit_black_24dp.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── recycler_row.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
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── nikhilpanju
│ └── recyclerviewsample
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/*
5 | .DS_Store
6 | /build
7 | /captures
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RecyclerViewEnhanced
2 | Android Library to provide swipe, click and other functionality to RecyclerView
3 | ## Usage
4 |
5 | Add this to your build.gradle file
6 |
7 | ```
8 | dependencies {
9 | compile 'com.nikhilpanju.recyclerviewenhanced:recyclerviewenhanced:1.1.0'
10 | }
11 | ```
12 |
13 | ## Features
14 | * Supports API 14+ (Earlier APIs not tested
15 | * Supports any view for "Swipe Options"
16 | * Doesn't require any new adapters or new views. Works with any existing RecyclerViews.
17 | * Requires adding `OnItemTouchListener` to the RecyclerView
18 | * Supports clicking and swiping functionalities.
19 | * Supports disabling clicking and swiping for particular items/rows.
20 | * Supports `independentViews` in your items/rows (Read below for more information)
21 | * Supports `fadeViews` in your items/rows (Read below for more information)
22 |
23 | ## Demo
24 | Build the sample application to try RecyclerViewEnhanced
25 | 
26 |
27 | ## Configuring
28 | * #### Create an instance of `RecyclerTouchListener`
29 | `onTouchListener = new RecyclerTouchListener(this, mRecyclerView);`
30 |
31 | * #### Set `IndependentViews` and `FadeViews` (If required)
32 | `IndependentViews` are views which can be clicked separately from the entire row. Their clicks have different functionality from row clicks. `FadeViews` are views which fade in and out as the rows are swiped closed and opened respectively.
33 |
34 | ```
35 | onTouchListener.setIndependentViews(R.id.rowButton)
36 | .setViewsToFade(R.id.rowButton)
37 | ```
38 |
39 | * #### Implement `OnRowClickListener` using `setClickable()`
40 | `setClickable()` will enable clicks for the recycler view items and the `IndependentViews`
41 |
42 | ```
43 | .setClickable(new RecyclerTouchListener.OnRowClickListener() {
44 | @Override
45 | public void onRowClicked(int position) {
46 | // Do something
47 | }
48 |
49 | @Override
50 | public void onIndependentViewClicked(int independentViewID, int position) {
51 | // Do something
52 | }
53 | })
54 | ```
55 |
56 | * #### Enable Swipe Functionality
57 |
58 | Set the views for which you require a click listener and enable swiping by using `setSwipeable()`
59 | ```
60 | .setSwipeOptionViews(R.id.add, R.id.edit, R.id.change)
61 | .setSwipeable(R.id.rowFG, R.id.rowBG, new RecyclerTouchListener.OnSwipeOptionsClickListener() {
62 | @Override
63 | public void onSwipeOptionClicked(int viewID, int position) {
64 | if (viewID == R.id.add) {
65 | // Do something
66 | } else if (viewID == R.id.edit) {
67 | // Do something
68 | } else if (viewID == R.id.change) {
69 | // Do something
70 | }
71 | }
72 | });
73 | ```
74 |
75 | * #### Adding the listener to the RecyclerView
76 |
77 | In `onResume()` add the listener:
78 | ```
79 | mRecyclerView.addOnItemTouchListener(onTouchListener);
80 | ```
81 | In `onPause()` remove the listener:
82 | ```
83 | mRecyclerView.removeOnItemTouchListener(onTouchListener);
84 | ```
85 |
86 | ## Additional Functionality
87 | * Use `onRowLongClickListener` to receive long click events
88 | ```
89 | .setLongClickable(true, new RecyclerTouchListener.OnRowLongClickListener() {
90 | @Override
91 | public void onRowLongClicked(int position) {
92 | ToastUtil.makeToast(getApplicationContext(), "Row " + (position + 1) + " long clicked!");
93 | }
94 | })
95 | ```
96 |
97 | * Use `setUnSwipeableRows()` to disable certain rows from swiping. Using this also displays an "difficult-to-slide" animation when trying to slide an unswipeable row.
98 | * Use `setUnClickableRows()` to disable click actions for certain rows. (Note: This also prevents the independentViews from being clicked).
99 | * `openSwipeOptions()` opens the swipe options for a specific row.
100 | * `closeVisibleBG()` closes any open options.
101 | * Implement `OnSwipeListener` to get `onSwipeOptionsClosed()` and `onSwipeOptionsOpened()` events.
102 |
103 |
104 | ### Closing swipe options when clicked anywhere outside of the recyclerView:
105 | * Make your Activity implement `RecyclerTouchListener.RecyclerTouchListenerHelper` and store the touchListener
106 | ```
107 | private OnActivityTouchListener touchListener;
108 |
109 | @Override
110 | public void setOnActivityTouchListener(OnActivityTouchListener listener) {
111 | this.touchListener = listener;
112 | }
113 | ```
114 | * Override `dispatchTouchEvent()` of your Activity and pass the `MotionEvent` variable to the `touchListener`
115 | ```
116 | @Override
117 | public boolean dispatchTouchEvent(MotionEvent ev) {
118 | if (touchListener != null) touchListener.getTouchCoordinates(ev);
119 | return super.dispatchTouchEvent(ev);
120 | }
121 | ```
122 | ## Author
123 | * Nikhil Panju ([Github](https://github.com/nikhilpanju))
124 |
125 |
126 | ## License
127 | Copyright 2016 Nikhil Panju
128 |
129 | Licensed under the Apache License, Version 2.0 (the "License");
130 | you may not use this file except in compliance with the License.
131 | You may obtain a copy of the License at
132 |
133 | ([http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
134 |
135 | Unless required by applicable law or agreed to in writing, software
136 | distributed under the License is distributed on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138 | See the License for the specific language governing permissions and
139 | limitations under the License.
140 |
--------------------------------------------------------------------------------
/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:2.1.0'
9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4'
10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/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/nikhilpanju/RecyclerViewEnhanced/da8117365dded9508be7cb96c83ca99d64907065/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 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.10-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 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/recyclerviewenhanced/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/recyclerviewenhanced/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | ext {
4 | bintrayRepo = 'maven'
5 | bintrayName = 'recyclerviewenhanced'
6 |
7 | publishedGroupId = 'com.nikhilpanju.recyclerviewenhanced'
8 | libraryName = 'recyclerviewenhanced'
9 | artifact = 'recyclerviewenhanced'
10 |
11 | libraryDescription = 'Android Library to provide swipe, click and other functionality to RecyclerView'
12 |
13 | siteUrl = 'https://github.com/nikhilpanju/RecyclerViewEnhanced'
14 | gitUrl = 'https://github.com/nikhilpanju/RecyclerViewEnhanced.git'
15 |
16 | libraryVersion = '1.0.1'
17 |
18 | developerId = 'nikhilpanju'
19 | developerName = 'Nikhil Panju'
20 | developerEmail = 'nikhilpanju22@gmail.com'
21 |
22 | licenseName = 'The Apache Software License, Version 2.0'
23 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
24 | allLicenses = ''
25 | }
26 |
27 | android {
28 | compileSdkVersion 23
29 | buildToolsVersion "23.0.3"
30 |
31 | defaultConfig {
32 | minSdkVersion 14
33 | targetSdkVersion 23
34 | versionCode 1
35 | versionName "1.0.1"
36 | }
37 | buildTypes {
38 | release {
39 | minifyEnabled false
40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
41 | }
42 | }
43 | }
44 |
45 | dependencies {
46 | compile fileTree(dir: 'libs', include: ['*.jar'])
47 | testCompile 'junit:junit:4.12'
48 | compile 'com.android.support:appcompat-v7:23.4.0'
49 | compile 'com.android.support:recyclerview-v7:23.4.0'
50 | }
51 |
52 | // Place it at the end of the file
53 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
54 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
55 |
--------------------------------------------------------------------------------
/recyclerviewenhanced/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\nikhi_000\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/recyclerviewenhanced/src/androidTest/java/com/nikhilpanju/recyclerviewenhanced/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.nikhilpanju.recyclerviewenhanced;
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 | }
--------------------------------------------------------------------------------
/recyclerviewenhanced/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/recyclerviewenhanced/src/main/java/com/nikhilpanju/recyclerviewenhanced/OnActivityTouchListener.java:
--------------------------------------------------------------------------------
1 | package com.nikhilpanju.recyclerviewenhanced;
2 |
3 | import android.view.MotionEvent;
4 |
5 | public interface OnActivityTouchListener {
6 | void getTouchCoordinates(MotionEvent ev);
7 | }
--------------------------------------------------------------------------------
/recyclerviewenhanced/src/main/java/com/nikhilpanju/recyclerviewenhanced/RecyclerTouchListener.java:
--------------------------------------------------------------------------------
1 | package com.nikhilpanju.recyclerviewenhanced;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ObjectAnimator;
5 | import android.app.Activity;
6 | import android.content.Context;
7 | import android.graphics.Rect;
8 | import android.os.Handler;
9 | import android.os.Vibrator;
10 | import android.support.v7.widget.RecyclerView;
11 | import android.util.DisplayMetrics;
12 | import android.util.Log;
13 | import android.view.MotionEvent;
14 | import android.view.VelocityTracker;
15 | import android.view.View;
16 | import android.view.ViewConfiguration;
17 | import android.view.animation.DecelerateInterpolator;
18 | import android.widget.ListView;
19 |
20 | import java.util.ArrayList;
21 | import java.util.Arrays;
22 | import java.util.HashSet;
23 | import java.util.List;
24 | import java.util.Set;
25 |
26 | public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener, OnActivityTouchListener {
27 | private static final String TAG = "RecyclerTouchListener";
28 | final Handler handler = new Handler();
29 | Activity act;
30 | List unSwipeableRows;
31 | /*
32 | * independentViews are views on the foreground layer which when clicked, act "independent" from the foreground
33 | * ie, they are treated separately from the "row click" action
34 | */
35 | List independentViews;
36 | List unClickableRows;
37 | List optionViews;
38 | Set ignoredViewTypes;
39 | // Cached ViewConfiguration and system-wide constant values
40 | private int touchSlop;
41 | private int minFlingVel;
42 | private int maxFlingVel;
43 | private long ANIMATION_STANDARD = 300;
44 | private long ANIMATION_CLOSE = 150;
45 | // Fixed properties
46 | private RecyclerView rView;
47 | // private SwipeListener mSwipeListener;
48 | private int bgWidth = 1, bgWidthLeft = 1; // 1 and not 0 to prevent dividing by zero
49 | // Transient properties
50 | // private List mPendingDismisses = new ArrayList<>();
51 | private int mDismissAnimationRefCount = 0;
52 | private float touchedX;
53 | private float touchedY;
54 | private boolean isFgSwiping;
55 | private int mSwipingSlop;
56 | private VelocityTracker mVelocityTracker;
57 | private int touchedPosition;
58 | private View touchedView;
59 | private boolean mPaused;
60 | private boolean bgVisible, fgPartialViewClicked;
61 | private int bgVisiblePosition;
62 | private View bgVisibleView;
63 | private boolean isRViewScrolling;
64 | private int heightOutsideRView, screenHeight;
65 | private boolean mLongClickPerformed;
66 | // Foreground view (to be swiped), Background view (to show)
67 | private View fgView;
68 | private View bgView;
69 | //view ID
70 | private int fgViewID;
71 | private int bgViewID, bgViewIDLeft;
72 | private ArrayList fadeViews;
73 | private OnRowClickListener mRowClickListener;
74 | private OnRowLongClickListener mRowLongClickListener;
75 | private OnSwipeOptionsClickListener mBgClickListener, mBgClickListenerLeft;
76 | // user choices
77 | private boolean clickable = false;
78 | private boolean longClickable = false;
79 | private boolean swipeable = false, swipeableLeftOptions = false;
80 | private int LONG_CLICK_DELAY = 800;
81 | private boolean longClickVibrate;
82 | Runnable mLongPressed = new Runnable() {
83 | public void run() {
84 | if (!longClickable)
85 | return;
86 |
87 | mLongClickPerformed = true;
88 |
89 | if (!bgVisible && touchedPosition >= 0 && !unClickableRows.contains(touchedPosition) && !isRViewScrolling) {
90 | if (longClickVibrate) {
91 | Vibrator vibe = (Vibrator) act.getSystemService(Context.VIBRATOR_SERVICE);
92 | vibe.vibrate(100);
93 | }
94 | mRowLongClickListener.onRowLongClicked(touchedPosition);
95 | }
96 | }
97 | };
98 |
99 | private RecyclerTouchListener() {
100 | }
101 |
102 | public RecyclerTouchListener(Activity a, RecyclerView recyclerView) {
103 | this.act = a;
104 | ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext());
105 | touchSlop = vc.getScaledTouchSlop();
106 | minFlingVel = vc.getScaledMinimumFlingVelocity() * 16;
107 | maxFlingVel = vc.getScaledMaximumFlingVelocity();
108 | rView = recyclerView;
109 | bgVisible = false;
110 | bgVisiblePosition = -1;
111 | bgVisibleView = null;
112 | fgPartialViewClicked = false;
113 | unSwipeableRows = new ArrayList<>();
114 | unClickableRows = new ArrayList<>();
115 | ignoredViewTypes = new HashSet<>();
116 | independentViews = new ArrayList<>();
117 | optionViews = new ArrayList<>();
118 | fadeViews = new ArrayList<>();
119 | isRViewScrolling = false;
120 |
121 | // mSwipeListener = listener;
122 |
123 | rView.addOnScrollListener(new RecyclerView.OnScrollListener() {
124 | @Override
125 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
126 | /**
127 | * This will ensure that this RecyclerTouchListener is paused during recycler view scrolling.
128 | * If a scroll listener is already assigned, the caller should still pass scroll changes through
129 | * to this listener.
130 | */
131 | setEnabled(newState != RecyclerView.SCROLL_STATE_DRAGGING);
132 |
133 | /**
134 | * This is used so that clicking a row cannot be done while scrolling
135 | */
136 | isRViewScrolling = newState != RecyclerView.SCROLL_STATE_IDLE;
137 | }
138 |
139 | @Override
140 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
141 |
142 | }
143 | });
144 | }
145 |
146 | /**
147 | * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
148 | *
149 | * @param enabled Whether or not to watch for gestures.
150 | */
151 | public void setEnabled(boolean enabled) {
152 | mPaused = !enabled;
153 | }
154 |
155 | @Override
156 | public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent motionEvent) {
157 | return handleTouchEvent(motionEvent);
158 | }
159 |
160 | @Override
161 | public void onTouchEvent(RecyclerView rv, MotionEvent motionEvent) {
162 | handleTouchEvent(motionEvent);
163 | }
164 |
165 | /*////////////// Clickable ////////////////////*/
166 |
167 | public RecyclerTouchListener setClickable(OnRowClickListener listener) {
168 | this.clickable = true;
169 | this.mRowClickListener = listener;
170 | return this;
171 | }
172 |
173 | public RecyclerTouchListener setClickable(boolean clickable) {
174 | this.clickable = clickable;
175 | return this;
176 | }
177 |
178 | public RecyclerTouchListener setLongClickable(boolean vibrate, OnRowLongClickListener listener) {
179 | this.longClickable = true;
180 | this.mRowLongClickListener = listener;
181 | this.longClickVibrate = vibrate;
182 | return this;
183 | }
184 | public RecyclerTouchListener setLongClickable(boolean longClickable) {
185 | this.longClickable = longClickable;
186 | return this;
187 | }
188 |
189 | public RecyclerTouchListener setIndependentViews(Integer... viewIds) {
190 | this.independentViews = new ArrayList<>(Arrays.asList(viewIds));
191 | return this;
192 | }
193 |
194 | public RecyclerTouchListener setUnClickableRows(Integer... rows) {
195 | this.unClickableRows = new ArrayList<>(Arrays.asList(rows));
196 | return this;
197 | }
198 |
199 | public RecyclerTouchListener setIgnoredViewTypes(Integer... viewTypes) {
200 | ignoredViewTypes.clear();
201 | ignoredViewTypes.addAll(Arrays.asList(viewTypes));
202 | return this;
203 | }
204 |
205 | //////////////// Swipeable ////////////////////
206 |
207 | public RecyclerTouchListener setSwipeable(int foregroundID, int backgroundID, OnSwipeOptionsClickListener listener) {
208 | this.swipeable = true;
209 | if (fgViewID != 0 && foregroundID != fgViewID)
210 | throw new IllegalArgumentException("foregroundID does not match previously set ID");
211 | fgViewID = foregroundID;
212 | bgViewID = backgroundID;
213 | this.mBgClickListener = listener;
214 |
215 | if (act instanceof RecyclerTouchListenerHelper)
216 | ((RecyclerTouchListenerHelper) act).setOnActivityTouchListener(this);
217 |
218 | DisplayMetrics displaymetrics = new DisplayMetrics();
219 | act.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
220 | screenHeight = displaymetrics.heightPixels;
221 |
222 | return this;
223 | }
224 |
225 | /*public RecyclerTouchListener setLeftToRightSwipeable(int foregroundID, int backgroundID, OnSwipeOptionsClickListener listener) {
226 | this.swipeableLeftOptions = true;
227 | if (fgViewID != 0 && foregroundID != fgViewID)
228 | throw new IllegalArgumentException("foregroundID does not match previously set ID");
229 | fgViewID = foregroundID;
230 | bgViewIDLeft = backgroundID;
231 | this.mBgClickListenerLeft = listener;
232 |
233 | if (act instanceof RecyclerTouchListenerHelper)
234 | ((RecyclerTouchListenerHelper) act).setOnActivityTouchListener(this);
235 |
236 | DisplayMetrics displaymetrics = new DisplayMetrics();
237 | act.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
238 | screenHeight = displaymetrics.heightPixels;
239 |
240 | return this;
241 | }*/
242 |
243 | public RecyclerTouchListener setSwipeable(boolean value) {
244 | this.swipeable = value;
245 | if (!value)
246 | invalidateSwipeOptions();
247 | return this;
248 | }
249 |
250 | public RecyclerTouchListener setSwipeOptionViews(Integer... viewIds) {
251 | this.optionViews = new ArrayList<>(Arrays.asList(viewIds));
252 | return this;
253 | }
254 |
255 | public RecyclerTouchListener setUnSwipeableRows(Integer... rows) {
256 | this.unSwipeableRows = new ArrayList<>(Arrays.asList(rows));
257 | return this;
258 | }
259 |
260 | //////////////// Fade Views ////////////////////
261 |
262 | // Set views which are faded out as fg is opened
263 | public RecyclerTouchListener setViewsToFade(Integer... viewIds) {
264 | this.fadeViews = new ArrayList<>(Arrays.asList(viewIds));
265 | return this;
266 | }
267 |
268 | // the entire foreground is faded out as it is opened
269 | public RecyclerTouchListener setFgFade() {
270 | if (!fadeViews.contains(fgViewID))
271 | this.fadeViews.add(fgViewID);
272 | return this;
273 | }
274 |
275 | //-------------- Checkers for preventing ---------------//
276 |
277 | private boolean isIndependentViewClicked(MotionEvent motionEvent) {
278 | for (int i = 0; i < independentViews.size(); i++) {
279 | if (touchedView != null) {
280 | Rect rect = new Rect();
281 | int x = (int) motionEvent.getRawX();
282 | int y = (int) motionEvent.getRawY();
283 | touchedView.findViewById(independentViews.get(i)).getGlobalVisibleRect(rect);
284 | if (rect.contains(x, y)) {
285 | return false;
286 | }
287 | }
288 | }
289 | return true;
290 | }
291 |
292 | private int getOptionViewID(MotionEvent motionEvent) {
293 | for (int i = 0; i < optionViews.size(); i++) {
294 | if (touchedView != null) {
295 | Rect rect = new Rect();
296 | int x = (int) motionEvent.getRawX();
297 | int y = (int) motionEvent.getRawY();
298 | touchedView.findViewById(optionViews.get(i)).getGlobalVisibleRect(rect);
299 | if (rect.contains(x, y)) {
300 | return optionViews.get(i);
301 | }
302 | }
303 | }
304 | return -1;
305 | }
306 |
307 | private int getIndependentViewID(MotionEvent motionEvent) {
308 | for (int i = 0; i < independentViews.size(); i++) {
309 | if (touchedView != null) {
310 | Rect rect = new Rect();
311 | int x = (int) motionEvent.getRawX();
312 | int y = (int) motionEvent.getRawY();
313 | touchedView.findViewById(independentViews.get(i)).getGlobalVisibleRect(rect);
314 | if (rect.contains(x, y)) {
315 | return independentViews.get(i);
316 | }
317 | }
318 | }
319 | return -1;
320 | }
321 |
322 | @Override
323 | public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
324 |
325 | }
326 |
327 | public void invalidateSwipeOptions() {
328 | bgWidth = 1;
329 | }
330 |
331 | public void openSwipeOptions(int position) {
332 | if (!swipeable || rView.getChildAt(position) == null
333 | || unSwipeableRows.contains(position) || shouldIgnoreAction(position))
334 | return;
335 | if (bgWidth < 2) {
336 | if (act.findViewById(bgViewID) != null)
337 | bgWidth = act.findViewById(bgViewID).getWidth();
338 | heightOutsideRView = screenHeight - rView.getHeight();
339 | }
340 | touchedPosition = position;
341 | touchedView = rView.getChildAt(position);
342 | fgView = touchedView.findViewById(fgViewID);
343 | bgView = touchedView.findViewById(bgViewID);
344 | bgView.setMinimumHeight(fgView.getHeight());
345 |
346 | closeVisibleBG(null);
347 | animateFG(touchedView, Animation.OPEN, ANIMATION_STANDARD);
348 | bgVisible = true;
349 | bgVisibleView = fgView;
350 | bgVisiblePosition = touchedPosition;
351 | }
352 |
353 | @Deprecated
354 | public void closeVisibleBG() {
355 | if (bgVisibleView == null) {
356 | Log.e(TAG, "No rows found for which background options are visible");
357 | return;
358 | }
359 | bgVisibleView.animate()
360 | .translationX(0)
361 | .setDuration(ANIMATION_CLOSE)
362 | .setListener(null);
363 |
364 | animateFadeViews(bgVisibleView, 1f, ANIMATION_CLOSE);
365 | bgVisible = false;
366 | bgVisibleView = null;
367 | bgVisiblePosition = -1;
368 | }
369 |
370 | public void closeVisibleBG(final OnSwipeListener mSwipeCloseListener) {
371 | if (bgVisibleView == null) {
372 | Log.e(TAG, "No rows found for which background options are visible");
373 | return;
374 | }
375 | final ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(bgVisibleView,
376 | View.TRANSLATION_X, 0f);
377 | translateAnimator.setDuration(ANIMATION_CLOSE);
378 | translateAnimator.addListener(new Animator.AnimatorListener() {
379 | @Override
380 | public void onAnimationStart(Animator animation) {
381 | }
382 |
383 | @Override
384 | public void onAnimationEnd(Animator animation) {
385 | if (mSwipeCloseListener != null)
386 | mSwipeCloseListener.onSwipeOptionsClosed();
387 | translateAnimator.removeAllListeners();
388 | }
389 |
390 | @Override
391 | public void onAnimationCancel(Animator animation) {
392 | }
393 |
394 | @Override
395 | public void onAnimationRepeat(Animator animation) {
396 | }
397 | });
398 | translateAnimator.start();
399 |
400 | animateFadeViews(bgVisibleView, 1f, ANIMATION_CLOSE);
401 | bgVisible = false;
402 | bgVisibleView = null;
403 | bgVisiblePosition = -1;
404 | }
405 |
406 | private void animateFadeViews(View downView, float alpha, long duration) {
407 | if (fadeViews != null) {
408 | for (final int viewID : fadeViews) {
409 | downView.findViewById(viewID).animate()
410 | .alpha(alpha)
411 | .setDuration(duration);
412 | }
413 | }
414 | }
415 |
416 | private void animateFG(View downView, Animation animateType, long duration) {
417 | if (animateType == Animation.OPEN) {
418 | ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(
419 | fgView, View.TRANSLATION_X, -bgWidth);
420 | translateAnimator.setDuration(duration);
421 | translateAnimator.setInterpolator(new DecelerateInterpolator(1.5f));
422 | translateAnimator.start();
423 | animateFadeViews(downView, 0f, duration);
424 | } else if (animateType == Animation.CLOSE) {
425 | ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(
426 | fgView, View.TRANSLATION_X, 0f);
427 | translateAnimator.setDuration(duration);
428 | translateAnimator.setInterpolator(new DecelerateInterpolator(1.5f));
429 | translateAnimator.start();
430 | animateFadeViews(downView, 1f, duration);
431 | }
432 | }
433 |
434 | private void animateFG(View downView, final Animation animateType, long duration,
435 | final OnSwipeListener mSwipeCloseListener) {
436 | final ObjectAnimator translateAnimator;
437 | if (animateType == Animation.OPEN) {
438 | translateAnimator = ObjectAnimator.ofFloat(fgView, View.TRANSLATION_X, -bgWidth);
439 | translateAnimator.setDuration(duration);
440 | translateAnimator.setInterpolator(new DecelerateInterpolator(1.5f));
441 | translateAnimator.start();
442 | animateFadeViews(downView, 0f, duration);
443 | } else /*if (animateType == Animation.CLOSE)*/ {
444 | translateAnimator = ObjectAnimator.ofFloat(fgView, View.TRANSLATION_X, 0f);
445 | translateAnimator.setDuration(duration);
446 | translateAnimator.setInterpolator(new DecelerateInterpolator(1.5f));
447 | translateAnimator.start();
448 | animateFadeViews(downView, 1f, duration);
449 | }
450 |
451 | translateAnimator.addListener(new Animator.AnimatorListener() {
452 | @Override
453 | public void onAnimationStart(Animator animation) {
454 | }
455 |
456 | @Override
457 | public void onAnimationEnd(Animator animation) {
458 | if (mSwipeCloseListener != null) {
459 | if (animateType == Animation.OPEN)
460 | mSwipeCloseListener.onSwipeOptionsOpened();
461 | else if (animateType == Animation.CLOSE)
462 | mSwipeCloseListener.onSwipeOptionsClosed();
463 | }
464 | translateAnimator.removeAllListeners();
465 | }
466 |
467 | @Override
468 | public void onAnimationCancel(Animator animation) {
469 | }
470 |
471 | @Override
472 | public void onAnimationRepeat(Animator animation) {
473 | }
474 | });
475 | }
476 |
477 | private boolean handleTouchEvent(MotionEvent motionEvent) {
478 | if (swipeable && bgWidth < 2) {
479 | // bgWidth = rView.getWidth();
480 | if (act.findViewById(bgViewID) != null)
481 | bgWidth = act.findViewById(bgViewID).getWidth();
482 |
483 | heightOutsideRView = screenHeight - rView.getHeight();
484 | }
485 |
486 | switch (motionEvent.getActionMasked()) {
487 |
488 | // When finger touches screen
489 | case MotionEvent.ACTION_DOWN: {
490 | if (mPaused) {
491 | break;
492 | }
493 |
494 | // Find the child view that was touched (perform a hit test)
495 | Rect rect = new Rect();
496 | int childCount = rView.getChildCount();
497 | int[] listViewCoords = new int[2];
498 | rView.getLocationOnScreen(listViewCoords);
499 | // x and y values respective to the recycler view
500 | int x = (int) motionEvent.getRawX() - listViewCoords[0];
501 | int y = (int) motionEvent.getRawY() - listViewCoords[1];
502 | View child;
503 |
504 | /*
505 | * check for every child (row) in the recycler view whether the touched co-ordinates belong to that
506 | * respective child and if it does, register that child as the touched view (touchedView)
507 | */
508 | for (int i = 0; i < childCount; i++) {
509 | child = rView.getChildAt(i);
510 | child.getHitRect(rect);
511 | if (rect.contains(x, y)) {
512 | touchedView = child;
513 | break;
514 | }
515 | }
516 |
517 | if (touchedView != null) {
518 | touchedX = motionEvent.getRawX();
519 | touchedY = motionEvent.getRawY();
520 | touchedPosition = rView.getChildAdapterPosition(touchedView);
521 |
522 | if (shouldIgnoreAction(touchedPosition)) {
523 | touchedPosition = ListView.INVALID_POSITION;
524 | return false; // <-- guard here allows for ignoring events, allowing more than one view type and preventing NPE
525 | }
526 |
527 | if (longClickable) {
528 | mLongClickPerformed = false;
529 | handler.postDelayed(mLongPressed, LONG_CLICK_DELAY);
530 | }
531 | if (swipeable) {
532 | mVelocityTracker = VelocityTracker.obtain();
533 | mVelocityTracker.addMovement(motionEvent);
534 | fgView = touchedView.findViewById(fgViewID);
535 | bgView = touchedView.findViewById(bgViewID);
536 | // bgView.getLayoutParams().height = fgView.getHeight();
537 | bgView.setMinimumHeight(fgView.getHeight());
538 |
539 | /*
540 | * bgVisible is true when the options menu is opened
541 | * This block is to register fgPartialViewClicked status - Partial view is the view that is still
542 | * shown on the screen if the options width is < device width
543 | */
544 | if (bgVisible && fgView != null) {
545 | handler.removeCallbacks(mLongPressed);
546 | x = (int) motionEvent.getRawX();
547 | y = (int) motionEvent.getRawY();
548 | fgView.getGlobalVisibleRect(rect);
549 | fgPartialViewClicked = rect.contains(x, y);
550 | } else {
551 | fgPartialViewClicked = false;
552 | }
553 | }
554 | }
555 |
556 | /*
557 | * If options menu is shown and the touched position is not the same as the row for which the
558 | * options is displayed - close the options menu for the row which is displaying it
559 | * (bgVisibleView and bgVisiblePosition is used for this purpose which registers which view and
560 | * which position has it's options menu opened)
561 | */
562 | x = (int) motionEvent.getRawX();
563 | y = (int) motionEvent.getRawY();
564 | rView.getHitRect(rect);
565 | if (swipeable && bgVisible && touchedPosition != bgVisiblePosition) {
566 | handler.removeCallbacks(mLongPressed);
567 | closeVisibleBG(null);
568 | }
569 | break;
570 | }
571 |
572 | case MotionEvent.ACTION_CANCEL: {
573 | handler.removeCallbacks(mLongPressed);
574 | if (mLongClickPerformed)
575 | break;
576 |
577 | if (mVelocityTracker == null) {
578 | break;
579 | }
580 | if (swipeable) {
581 | if (touchedView != null && isFgSwiping) {
582 | // cancel
583 | animateFG(touchedView, Animation.CLOSE, ANIMATION_STANDARD);
584 | }
585 | mVelocityTracker.recycle();
586 | mVelocityTracker = null;
587 | isFgSwiping = false;
588 | bgView = null;
589 | }
590 | touchedX = 0;
591 | touchedY = 0;
592 | touchedView = null;
593 | touchedPosition = ListView.INVALID_POSITION;
594 | break;
595 | }
596 |
597 | // When finger is lifted off the screen (after clicking, flinging, swiping, etc..)
598 | case MotionEvent.ACTION_UP: {
599 | handler.removeCallbacks(mLongPressed);
600 | if (mLongClickPerformed)
601 | break;
602 |
603 | if (mVelocityTracker == null && swipeable) {
604 | break;
605 | }
606 | if (touchedPosition < 0)
607 | break;
608 |
609 | // swipedLeft and swipedRight are true if the user swipes in the respective direction (no conditions)
610 | boolean swipedLeft = false;
611 | boolean swipedRight = false;
612 | /*
613 | * swipedLeftProper and swipedRightProper are true if user swipes in the respective direction
614 | * and if certain conditions are satisfied (given some few lines below)
615 | */
616 | boolean swipedLeftProper = false;
617 | boolean swipedRightProper = false;
618 |
619 | float mFinalDelta = motionEvent.getRawX() - touchedX;
620 |
621 | // mVelocityTracker.addMovement(motionEvent);
622 | // mVelocityTracker.computeCurrentVelocity(1000);
623 | // float velocityX = mVelocityTracker.getXVelocity();
624 | // float absVelocityX = Math.abs(velocityX);
625 | // float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
626 |
627 | // if swiped in a direction, make that respective variable true
628 | if (isFgSwiping) {
629 | swipedLeft = mFinalDelta < 0;
630 | swipedRight = mFinalDelta > 0;
631 | }
632 |
633 | /*
634 | * If the user has swiped more than half of the width of the options menu, or if the
635 | * velocity of swiping is between min and max fling values
636 | * "proper" variable are set true
637 | */
638 | if (Math.abs(mFinalDelta) > bgWidth / 2 && isFgSwiping) {
639 | swipedLeftProper = mFinalDelta < 0;
640 | swipedRightProper = mFinalDelta > 0;
641 | } else if (swipeable) {
642 | mVelocityTracker.addMovement(motionEvent);
643 | mVelocityTracker.computeCurrentVelocity(1000);
644 | float velocityX = mVelocityTracker.getXVelocity();
645 | float absVelocityX = Math.abs(velocityX);
646 | float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
647 | if (minFlingVel <= absVelocityX && absVelocityX <= maxFlingVel
648 | && absVelocityY < absVelocityX && isFgSwiping) {
649 | // dismiss only if flinging in the same direction as dragging
650 | swipedLeftProper = (velocityX < 0) == (mFinalDelta < 0);
651 | swipedRightProper = (velocityX > 0) == (mFinalDelta > 0);
652 | }
653 | }
654 |
655 | ///////// Manipulation of view based on the 4 variables mentioned above ///////////
656 |
657 | // if swiped left properly and options menu isn't already visible, animate the foreground to the left
658 | if (swipeable && !swipedRight && swipedLeftProper && touchedPosition != RecyclerView.NO_POSITION
659 | && !unSwipeableRows.contains(touchedPosition) && !bgVisible) {
660 |
661 | final View downView = touchedView; // touchedView gets null'd before animation ends
662 | final int downPosition = touchedPosition;
663 | ++mDismissAnimationRefCount;
664 | //TODO - speed
665 | animateFG(touchedView, Animation.OPEN, ANIMATION_STANDARD);
666 | bgVisible = true;
667 | bgVisibleView = fgView;
668 | bgVisiblePosition = downPosition;
669 | }
670 | // else if swiped right properly when options menu is visible, close the menu and bring the foreground
671 | // to it's original position
672 | else if (swipeable && !swipedLeft && swipedRightProper && touchedPosition != RecyclerView.NO_POSITION
673 | && !unSwipeableRows.contains(touchedPosition) && bgVisible) {
674 | // dismiss
675 | final View downView = touchedView; // touchedView gets null'd before animation ends
676 | final int downPosition = touchedPosition;
677 |
678 | ++mDismissAnimationRefCount;
679 | //TODO - speed
680 | animateFG(touchedView, Animation.CLOSE, ANIMATION_STANDARD);
681 | bgVisible = false;
682 | bgVisibleView = null;
683 | bgVisiblePosition = -1;
684 | }
685 | // else if swiped left incorrectly (not satisfying the above conditions), animate the foreground back to
686 | // it's original position (spring effect)
687 | else if (swipeable && swipedLeft && !bgVisible) {
688 | // cancel
689 | final View tempBgView = bgView;
690 | animateFG(touchedView, Animation.CLOSE, ANIMATION_STANDARD, new OnSwipeListener() {
691 | @Override
692 | public void onSwipeOptionsClosed() {
693 | if (tempBgView != null)
694 | tempBgView.setVisibility(View.VISIBLE);
695 | }
696 |
697 | @Override
698 | public void onSwipeOptionsOpened() {
699 |
700 | }
701 | });
702 |
703 | bgVisible = false;
704 | bgVisibleView = null;
705 | bgVisiblePosition = -1;
706 | }
707 | // else if swiped right incorrectly (not satisfying the above conditions), animate the foreground to
708 | // it's open position (spring effect)
709 | else if (swipeable && swipedRight && bgVisible) {
710 | // cancel
711 | animateFG(touchedView, Animation.OPEN, ANIMATION_STANDARD);
712 | bgVisible = true;
713 | bgVisibleView = fgView;
714 | bgVisiblePosition = touchedPosition;
715 | }
716 | // This case deals with an error where the user can swipe left, then right
717 | // really fast and the fg is stuck open - so in that case we close the fg
718 | else if (swipeable && swipedRight && !bgVisible) {
719 | // cancel
720 | animateFG(touchedView, Animation.CLOSE, ANIMATION_STANDARD);
721 | bgVisible = false;
722 | bgVisibleView = null;
723 | bgVisiblePosition = -1;
724 | }
725 | // This case deals with an error where the user can swipe right, then left
726 | // really fast and the fg is stuck open - so in that case we open the fg
727 | else if (swipeable && swipedLeft && bgVisible) {
728 | // cancel
729 | animateFG(touchedView, Animation.OPEN, ANIMATION_STANDARD);
730 | bgVisible = true;
731 | bgVisibleView = fgView;
732 | bgVisiblePosition = touchedPosition;
733 | }
734 |
735 | // if clicked
736 | else if (!swipedRight && !swipedLeft) {
737 | // if partial foreground view is clicked (see ACTION_DOWN) bring foreground back to original position
738 | // bgVisible is true automatically since it's already checked in ACTION_DOWN block
739 | if (swipeable && fgPartialViewClicked) {
740 | animateFG(touchedView, Animation.CLOSE, ANIMATION_STANDARD);
741 | bgVisible = false;
742 | bgVisibleView = null;
743 | bgVisiblePosition = -1;
744 | }
745 | // On Click listener for rows
746 | else if (clickable && !bgVisible && touchedPosition >= 0 && !unClickableRows.contains(touchedPosition)
747 | && isIndependentViewClicked(motionEvent) && !isRViewScrolling) {
748 | mRowClickListener.onRowClicked(touchedPosition);
749 | }
750 | // On Click listener for independent views inside the rows
751 | else if (clickable && !bgVisible && touchedPosition >= 0 && !unClickableRows.contains(touchedPosition)
752 | && !isIndependentViewClicked(motionEvent) && !isRViewScrolling) {
753 | final int independentViewID = getIndependentViewID(motionEvent);
754 | if (independentViewID >= 0)
755 | mRowClickListener.onIndependentViewClicked(independentViewID, touchedPosition);
756 | }
757 | // On Click listener for background options
758 | else if (swipeable && bgVisible && !fgPartialViewClicked) {
759 | final int optionID = getOptionViewID(motionEvent);
760 | if (optionID >= 0 && touchedPosition >= 0) {
761 | final int downPosition = touchedPosition;
762 | closeVisibleBG(new OnSwipeListener() {
763 | @Override
764 | public void onSwipeOptionsClosed() {
765 | mBgClickListener.onSwipeOptionClicked(optionID, downPosition);
766 | }
767 |
768 | @Override
769 | public void onSwipeOptionsOpened() {
770 |
771 | }
772 | });
773 | }
774 | }
775 | }
776 | }
777 | // if clicked and not swiped
778 |
779 | if (swipeable) {
780 | mVelocityTracker.recycle();
781 | mVelocityTracker = null;
782 | }
783 | touchedX = 0;
784 | touchedY = 0;
785 | touchedView = null;
786 | touchedPosition = ListView.INVALID_POSITION;
787 | isFgSwiping = false;
788 | bgView = null;
789 | break;
790 |
791 | // when finger is moving across the screen (and not yet lifted)
792 | case MotionEvent.ACTION_MOVE: {
793 | if (mLongClickPerformed)
794 | break;
795 | if (mVelocityTracker == null || mPaused || !swipeable) {
796 | break;
797 | }
798 |
799 | mVelocityTracker.addMovement(motionEvent);
800 | float deltaX = motionEvent.getRawX() - touchedX;
801 | float deltaY = motionEvent.getRawY() - touchedY;
802 |
803 | /*
804 | * isFgSwiping variable which is set to true here is used to alter the swipedLeft, swipedRightProper
805 | * variables in "ACTION_UP" block by checking if user is actually swiping at present or not
806 | */
807 | if (!isFgSwiping && Math.abs(deltaX) > touchSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
808 | handler.removeCallbacks(mLongPressed);
809 | isFgSwiping = true;
810 | mSwipingSlop = (deltaX > 0 ? touchSlop : -touchSlop);
811 | }
812 |
813 | // This block moves the foreground along with the finger when swiping
814 | if (swipeable && isFgSwiping && !unSwipeableRows.contains(touchedPosition)) {
815 | if (bgView == null) {
816 | bgView = touchedView.findViewById(bgViewID);
817 | bgView.setVisibility(View.VISIBLE);
818 | }
819 | // if fg is being swiped left
820 | if (deltaX < touchSlop && !bgVisible) {
821 | float translateAmount = deltaX - mSwipingSlop;
822 | // if ((Math.abs(translateAmount) > bgWidth ? -bgWidth : translateAmount) <= 0) {
823 | // swipe fg till width of bg. If swiped further, nothing happens (stalls at width of bg)
824 | fgView.setTranslationX(Math.abs(translateAmount) > bgWidth ? -bgWidth : translateAmount);
825 | if (fgView.getTranslationX() > 0) fgView.setTranslationX(0);
826 | // }
827 |
828 | // fades all the fadeViews gradually to 0 alpha as dragged
829 | if (fadeViews != null) {
830 | for (int viewID : fadeViews) {
831 | touchedView.findViewById(viewID).setAlpha(1 - (Math.abs(translateAmount) / bgWidth));
832 | }
833 | }
834 | }
835 | // if fg is being swiped right
836 | else if (deltaX > 0 && bgVisible) {
837 | // for closing rightOptions
838 | if (bgVisible) {
839 | float translateAmount = (deltaX - mSwipingSlop) - bgWidth;
840 |
841 | // swipe fg till it reaches original position. If swiped further, nothing happens (stalls at 0)
842 | fgView.setTranslationX(translateAmount > 0 ? 0 : translateAmount);
843 |
844 | // fades all the fadeViews gradually to 0 alpha as dragged
845 | if (fadeViews != null) {
846 | for (int viewID : fadeViews) {
847 | touchedView.findViewById(viewID).setAlpha(1 - (Math.abs(translateAmount) / bgWidth));
848 | }
849 | }
850 | }
851 | // for opening leftOptions
852 | else {
853 | float translateAmount = (deltaX - mSwipingSlop) - bgWidth;
854 |
855 | // swipe fg till it reaches original position. If swiped further, nothing happens (stalls at 0)
856 | fgView.setTranslationX(translateAmount > 0 ? 0 : translateAmount);
857 |
858 | // fades all the fadeViews gradually to 0 alpha as dragged
859 | if (fadeViews != null) {
860 | for (int viewID : fadeViews) {
861 | touchedView.findViewById(viewID).setAlpha(1 - (Math.abs(translateAmount) / bgWidth));
862 | }
863 | }
864 | }
865 | }
866 | return true;
867 | }
868 | // moves the fg slightly to give the illusion of an "unswipeable" row
869 | else if (swipeable && isFgSwiping && unSwipeableRows.contains(touchedPosition)) {
870 | if (deltaX < touchSlop && !bgVisible) {
871 | float translateAmount = deltaX - mSwipingSlop;
872 | if (bgView == null)
873 | bgView = touchedView.findViewById(bgViewID);
874 |
875 | if (bgView != null)
876 | bgView.setVisibility(View.GONE);
877 |
878 | // swipe fg till width of bg. If swiped further, nothing happens (stalls at width of bg)
879 | fgView.setTranslationX(translateAmount / 5);
880 | if (fgView.getTranslationX() > 0) fgView.setTranslationX(0);
881 |
882 | // fades all the fadeViews gradually to 0 alpha as dragged
883 | // if (fadeViews != null) {
884 | // for (int viewID : fadeViews) {
885 | // touchedView.findViewById(viewID).setAlpha(1 - (Math.abs(translateAmount) / bgWidth));
886 | // }
887 | // }
888 | }
889 | return true;
890 | }
891 | break;
892 | }
893 | }
894 | return false;
895 | }
896 |
897 | /**
898 | * Gets coordinates from Activity and closes any
899 | * swiped rows if touch happens outside the recycler view
900 | */
901 | @Override
902 | public void getTouchCoordinates(MotionEvent ev) {
903 | int y = (int) ev.getRawY();
904 | if (swipeable && bgVisible && ev.getActionMasked() == MotionEvent.ACTION_DOWN
905 | && y < heightOutsideRView) closeVisibleBG(null);
906 | }
907 |
908 | private boolean shouldIgnoreAction(int touchedPosition) {
909 | return rView == null || ignoredViewTypes.contains(rView.getAdapter().getItemViewType(touchedPosition));
910 | }
911 |
912 | private enum Animation {
913 | OPEN, CLOSE
914 | }
915 |
916 | ///////////////////////////////////////////////////////////////////////////////////////
917 | //////////////////////////////////// Interfaces /////////////////////////////////////
918 | ///////////////////////////////////////////////////////////////////////////////////////
919 |
920 | public interface OnRowClickListener {
921 | void onRowClicked(int position);
922 |
923 | void onIndependentViewClicked(int independentViewID, int position);
924 | }
925 |
926 | public interface OnRowLongClickListener {
927 | void onRowLongClicked(int position);
928 | }
929 |
930 | public interface OnSwipeOptionsClickListener {
931 | void onSwipeOptionClicked(int viewID, int position);
932 | }
933 |
934 | public interface RecyclerTouchListenerHelper {
935 | void setOnActivityTouchListener(OnActivityTouchListener listener);
936 | }
937 |
938 | public interface OnSwipeListener {
939 | void onSwipeOptionsClosed();
940 |
941 | void onSwipeOptionsOpened();
942 | }
943 | }
944 |
--------------------------------------------------------------------------------
/recyclerviewenhanced/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RecyclerViewEnhanced
3 |
4 |
--------------------------------------------------------------------------------
/recyclerviewenhanced/src/test/java/com/nikhilpanju/recyclerviewenhanced/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.nikhilpanju.recyclerviewenhanced;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | applicationId "com.nikhilpanju.recyclerviewsample"
9 | minSdkVersion 14
10 | targetSdkVersion 23
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(include: ['*.jar'], dir: 'libs')
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:23.4.0'
26 | compile 'com.android.support:recyclerview-v7:23.4.0'
27 | compile project(':recyclerviewenhanced')
28 | }
29 |
--------------------------------------------------------------------------------
/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\nikhi_000\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/sample/src/androidTest/java/com/nikhilpanju/recyclerviewsample/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.nikhilpanju.recyclerviewsample;
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 | }
--------------------------------------------------------------------------------
/sample/src/common/images/Demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilpanju/RecyclerViewEnhanced/da8117365dded9508be7cb96c83ca99d64907065/sample/src/common/images/Demo.gif
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/nikhilpanju/recyclerviewsample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.nikhilpanju.recyclerviewsample;
2 |
3 | import android.content.Context;
4 | import android.content.DialogInterface;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AlertDialog;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.support.v7.widget.LinearLayoutManager;
9 | import android.support.v7.widget.RecyclerView;
10 | import android.view.LayoutInflater;
11 | import android.view.Menu;
12 | import android.view.MenuItem;
13 | import android.view.MotionEvent;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.widget.TextView;
17 |
18 | import com.nikhilpanju.recyclerviewenhanced.OnActivityTouchListener;
19 | import com.nikhilpanju.recyclerviewenhanced.RecyclerTouchListener;
20 |
21 | import java.util.ArrayList;
22 | import java.util.List;
23 |
24 | public class MainActivity extends AppCompatActivity implements RecyclerTouchListener.RecyclerTouchListenerHelper {
25 |
26 | RecyclerView mRecyclerView;
27 | MainAdapter mAdapter;
28 | String[] dialogItems;
29 | List unclickableRows, unswipeableRows;
30 | private RecyclerTouchListener onTouchListener;
31 | private int openOptionsPosition;
32 | private OnActivityTouchListener touchListener;
33 |
34 | @Override
35 | protected void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | setContentView(R.layout.activity_main);
38 |
39 | if (getSupportActionBar() != null)
40 | getSupportActionBar().setTitle("RecyclerViewEnhanced");
41 |
42 | unclickableRows = new ArrayList<>();
43 | unswipeableRows = new ArrayList<>();
44 | dialogItems = new String[25];
45 | for (int i = 0; i < 25; i++) {
46 | dialogItems[i] = String.valueOf(i + 1);
47 | }
48 |
49 | mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
50 | mAdapter = new MainAdapter(this, getData());
51 | mRecyclerView.setAdapter(mAdapter);
52 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
53 |
54 | onTouchListener = new RecyclerTouchListener(this, mRecyclerView);
55 | onTouchListener
56 | .setIndependentViews(R.id.rowButton)
57 | .setViewsToFade(R.id.rowButton)
58 | .setClickable(new RecyclerTouchListener.OnRowClickListener() {
59 | @Override
60 | public void onRowClicked(int position) {
61 | ToastUtil.makeToast(getApplicationContext(), "Row " + (position + 1) + " clicked!");
62 | }
63 |
64 | @Override
65 | public void onIndependentViewClicked(int independentViewID, int position) {
66 | ToastUtil.makeToast(getApplicationContext(), "Button in row " + (position + 1) + " clicked!");
67 | }
68 | })
69 | .setLongClickable(true, new RecyclerTouchListener.OnRowLongClickListener() {
70 | @Override
71 | public void onRowLongClicked(int position) {
72 | ToastUtil.makeToast(getApplicationContext(), "Row " + (position + 1) + " long clicked!");
73 | }
74 | })
75 | .setSwipeOptionViews(R.id.add, R.id.edit, R.id.change)
76 | .setSwipeable(R.id.rowFG, R.id.rowBG, new RecyclerTouchListener.OnSwipeOptionsClickListener() {
77 | @Override
78 | public void onSwipeOptionClicked(int viewID, int position) {
79 | String message = "";
80 | if (viewID == R.id.add) {
81 | message += "Add";
82 | } else if (viewID == R.id.edit) {
83 | message += "Edit";
84 | } else if (viewID == R.id.change) {
85 | message += "Change";
86 | }
87 | message += " clicked for row " + (position + 1);
88 | ToastUtil.makeToast(getApplicationContext(), message);
89 | }
90 | });
91 | }
92 |
93 | @Override
94 | protected void onResume() {
95 | super.onResume();
96 | mRecyclerView.addOnItemTouchListener(onTouchListener); }
97 |
98 | @Override
99 | protected void onPause() {
100 | super.onPause();
101 | mRecyclerView.removeOnItemTouchListener(onTouchListener);
102 | }
103 |
104 | private List getData() {
105 | List list = new ArrayList<>(25);
106 | for (int i = 0; i < 25; i++) {
107 | list.add(new RowModel("Row " + (i + 1), "Some Text... "));
108 | }
109 | return list;
110 | }
111 |
112 | @Override
113 | public boolean onCreateOptionsMenu(Menu menu) {
114 | getMenuInflater().inflate(R.menu.menu_main, menu);
115 | return super.onCreateOptionsMenu(menu);
116 | }
117 |
118 | @Override
119 | public boolean onOptionsItemSelected(MenuItem item) {
120 | boolean currentState = false;
121 | if (item.isCheckable()) {
122 | currentState = item.isChecked();
123 | item.setChecked(!currentState);
124 | }
125 | switch (item.getItemId()) {
126 | case R.id.menu_swipeable:
127 | onTouchListener.setSwipeable(!currentState);
128 | return true;
129 | case R.id.menu_clickable:
130 | onTouchListener.setClickable(!currentState);
131 | return true;
132 | case R.id.menu_unclickableRows:
133 | showMultiSelectDialog(unclickableRows, item.getItemId());
134 | return true;
135 | case R.id.menu_unswipeableRows:
136 | showMultiSelectDialog(unswipeableRows, item.getItemId());
137 | return true;
138 | case R.id.menu_openOptions:
139 | showSingleSelectDialog();
140 | return true;
141 | default:
142 | return super.onOptionsItemSelected(item);
143 | }
144 | }
145 |
146 | private void showMultiSelectDialog(final List list, final int menuId) {
147 | boolean[] checkedItems = new boolean[25];
148 | for (int i = 0; i < list.size(); i++) {
149 | checkedItems[list.get(i)] = true;
150 | }
151 |
152 | String title = "Select {} Rows";
153 | if (menuId == R.id.menu_unclickableRows) title = title.replace("{}", "Unclickable");
154 | else title = title.replace("{}", "Unswipeable");
155 |
156 | AlertDialog.Builder builder = new AlertDialog.Builder(this)
157 | .setTitle(title)
158 | .setMultiChoiceItems(dialogItems, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
159 | @Override
160 | public void onClick(DialogInterface dialog, int which, boolean isChecked) {
161 | if (isChecked)
162 | list.add(which);
163 | else
164 | list.remove(which);
165 | }
166 | })
167 | .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
168 | @Override
169 | public void onClick(DialogInterface dialog, int which) {
170 | Integer[] tempArray = new Integer[list.size()];
171 | if (menuId == R.id.menu_unclickableRows)
172 | onTouchListener.setUnClickableRows(list.toArray(tempArray));
173 | else
174 | onTouchListener.setUnSwipeableRows(list.toArray(tempArray));
175 | }
176 | });
177 | builder.create().show();
178 | }
179 |
180 | private void showSingleSelectDialog() {
181 | AlertDialog.Builder builder = new AlertDialog.Builder(this)
182 | .setTitle("Open Swipe Options for row: ")
183 | .setSingleChoiceItems(dialogItems, 0, new DialogInterface.OnClickListener() {
184 | @Override
185 | public void onClick(DialogInterface dialog, int which) {
186 | openOptionsPosition = which;
187 | }
188 | })
189 | .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
190 | @Override
191 | public void onClick(DialogInterface dialog, int which) {
192 | onTouchListener.openSwipeOptions(openOptionsPosition);
193 | }
194 | });
195 | builder.create().show();
196 | }
197 |
198 | @Override
199 | public boolean dispatchTouchEvent(MotionEvent ev) {
200 | if (touchListener != null) touchListener.getTouchCoordinates(ev);
201 | return super.dispatchTouchEvent(ev);
202 | }
203 |
204 | @Override
205 | public void setOnActivityTouchListener(OnActivityTouchListener listener) {
206 | this.touchListener = listener;
207 | }
208 |
209 | private class MainAdapter extends RecyclerView.Adapter {
210 | LayoutInflater inflater;
211 | List modelList;
212 |
213 | public MainAdapter(Context context, List list) {
214 | inflater = LayoutInflater.from(context);
215 | modelList = new ArrayList<>(list);
216 | }
217 |
218 | @Override
219 | public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
220 | View view = inflater.inflate(R.layout.recycler_row, parent, false);
221 | return new MainViewHolder(view);
222 | }
223 |
224 | @Override
225 | public void onBindViewHolder(MainViewHolder holder, int position) {
226 | holder.bindData(modelList.get(position));
227 | }
228 |
229 | @Override
230 | public int getItemCount() {
231 | return modelList.size();
232 | }
233 |
234 | class MainViewHolder extends RecyclerView.ViewHolder {
235 |
236 | TextView mainText, subText;
237 |
238 | public MainViewHolder(View itemView) {
239 | super(itemView);
240 | mainText = (TextView) itemView.findViewById(R.id.mainText);
241 | subText = (TextView) itemView.findViewById(R.id.subText);
242 | }
243 |
244 | public void bindData(RowModel rowModel) {
245 | mainText.setText(rowModel.getMainText());
246 | subText.setText(rowModel.getSubText());
247 | }
248 | }
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/nikhilpanju/recyclerviewsample/RowModel.java:
--------------------------------------------------------------------------------
1 | package com.nikhilpanju.recyclerviewsample;
2 |
3 | public class RowModel {
4 | String mainText, subText;
5 |
6 | public RowModel(String mainText, String subText) {
7 | this.mainText = mainText;
8 | this.subText = subText;
9 | }
10 |
11 | public String getMainText() {
12 | return mainText;
13 | }
14 |
15 | public void setMainText(String mainText) {
16 | this.mainText = mainText;
17 | }
18 |
19 | public String getSubText() {
20 | return subText;
21 | }
22 |
23 | public void setSubText(String subText) {
24 | this.subText = subText;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/nikhilpanju/recyclerviewsample/ToastUtil.java:
--------------------------------------------------------------------------------
1 | package com.nikhilpanju.recyclerviewsample;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | public class ToastUtil {
7 | public static void makeToast(Context context, String message){
8 | Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
9 | }
10 | public static void makeToast(Context context, String message, int duration){
11 | Toast.makeText(context, message, duration).show();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_add_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_build_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_mode_edit_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/recycler_row.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
18 |
26 |
27 |
34 |
35 |
41 |
42 |
55 |
56 |
57 |
58 |
66 |
67 |
74 |
75 |
81 |
82 |
95 |
96 |
97 |
98 |
106 |
107 |
114 |
115 |
121 |
122 |
135 |
136 |
137 |
138 |
139 |
149 |
150 |
158 |
159 |
165 |
166 |
175 |
176 |
183 |
184 |
185 |
193 |
194 |
195 |
196 |
197 |
203 |
204 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilpanju/RecyclerViewEnhanced/da8117365dded9508be7cb96c83ca99d64907065/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilpanju/RecyclerViewEnhanced/da8117365dded9508be7cb96c83ca99d64907065/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilpanju/RecyclerViewEnhanced/da8117365dded9508be7cb96c83ca99d64907065/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilpanju/RecyclerViewEnhanced/da8117365dded9508be7cb96c83ca99d64907065/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilpanju/RecyclerViewEnhanced/da8117365dded9508be7cb96c83ca99d64907065/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #36CF8E
8 | #3693D0
9 | #ff1744
10 |
11 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 85dp
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RecyclerViewEnhanced
3 |
4 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/sample/src/test/java/com/nikhilpanju/recyclerviewsample/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.nikhilpanju.recyclerviewsample;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sample', ':recyclerviewenhanced'
2 |
--------------------------------------------------------------------------------