├── .gitattributes ├── .github └── workflows │ └── android.yml ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── misc.xml └── runConfigurations.xml ├── LICENSE ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── liquidswipedemo │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── liquidswipedemo │ │ │ ├── CustomFragmentPagerAdapter.kt │ │ │ ├── CustomPagerAdapter.kt │ │ │ ├── DataValues.kt │ │ │ ├── DummyFragment.kt │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── fragment_dummy.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── raw │ │ ├── camera.json │ │ ├── luggage.json │ │ ├── map.json │ │ ├── mountain.json │ │ └── van.json │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── liquidswipedemo │ └── ExampleUnitTest.kt ├── build.gradle ├── demo screenshots ├── LiquidSwipeDemo.gif └── LiquidSwipeDemo_Touch_Interactive.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── liquidswipe ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jem │ │ └── liquidswipe │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jem │ │ │ └── liquidswipe │ │ │ ├── LiquidSwipeViewPager.kt │ │ │ ├── base │ │ │ ├── ClipPathProvider.kt │ │ │ └── LiquidSwipeLayout.kt │ │ │ ├── clippathprovider │ │ │ └── LiquidSwipeClipPathProvider.kt │ │ │ ├── layout │ │ │ ├── LiquidSwipeConstraintLayout.kt │ │ │ ├── LiquidSwipeFrameLayout.kt │ │ │ └── LiquidSwipeLinearLayout.kt │ │ │ └── util │ │ │ └── FixedSpeedScroller.kt │ └── res │ │ └── values │ │ └── attrs.xml │ └── test │ └── java │ └── com │ └── jem │ └── liquidswipe │ └── ExampleUnitTest.kt └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Make Gradle executable 17 | run: chmod +x ./gradlew 18 | - name: Build with Gradle 19 | run: ./gradlew build 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | # Uncomment the following line in case you need and you don't have the release build type files in your app 17 | # release/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # Log Files 30 | *.log 31 | 32 | # Android Studio Navigation editor temp files 33 | .navigation/ 34 | 35 | # Android Studio captures folder 36 | captures/ 37 | 38 | # IntelliJ 39 | *.iml 40 | .idea/workspace.xml 41 | .idea/tasks.xml 42 | .idea/gradle.xml 43 | .idea/assetWizardSettings.xml 44 | .idea/dictionaries 45 | .idea/libraries 46 | # Android Studio 3 in .gitignore file. 47 | .idea/caches 48 | .idea/modules.xml 49 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 50 | .idea/navEditor.xml 51 | 52 | # Keystore files 53 | # Uncomment the following lines if you do not want to check your keystore files in. 54 | #*.jks 55 | #*.keystore 56 | 57 | # External native build folder generated in Android Studio 2.2 and later 58 | .externalNativeBuild 59 | 60 | # Google Services (e.g. APIs or Firebase) 61 | # google-services.json 62 | 63 | # Freeline 64 | freeline.py 65 | freeline/ 66 | freeline_project_description.json 67 | 68 | # fastlane 69 | fastlane/report.xml 70 | fastlane/Preview.html 71 | fastlane/screenshots 72 | fastlane/test_output 73 | fastlane/readme.md 74 | 75 | # Version control 76 | vcs.xml 77 | 78 | # lint 79 | lint/intermediates/ 80 | lint/generated/ 81 | lint/outputs/ 82 | lint/tmp/ 83 | # lint/reports/ 84 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jem 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiquidSwipe 2 | Android LiquidSwipe Library 3 | 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-silver.svg)](https://opensource.org/licenses/MIT) [![](https://jitpack.io/v/Chrisvin/LiquidSwipe.svg)](https://jitpack.io/#Chrisvin/LiquidSwipe) [![API](https://img.shields.io/badge/API-21%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=21) 5 | 6 | | | | 7 | | :-: | :-: | 8 | | [Default](#usage) | [Touch Interactive](#touch-interactive---making-the-liquidswipe-wave-center-y-value-match-the-touch-y-value) | 9 | 10 | LiquidSwipe is a viewpager library that can be used to make awesome onboarding designs. ([Default Demo apk](https://github.com/Chrisvin/LiquidSwipe/releases/download/1.1/LiquidSwipeDemo.-.Default.apk)) ([TouchInteractive Demo apk](https://github.com/Chrisvin/LiquidSwipe/releases/download/1.1/LiquidSwipeDemo.-.Touch_Interactive.apk)) 11 | 12 | If you like this, you'll like [ConcentricOnboarding](https://github.com/Chrisvin/ConcentricOnboarding) as well. 13 | 14 | ## Demo app 15 | To run the demo project, clone the repository and run it via Android Studio. 16 |
(OR) 17 |
Download the latest demo apk from [releases](https://github.com/Chrisvin/LiquidSwipe/releases). 18 | 19 | ## Usage 20 | #### Set up the dependency 21 | 1. Add the JitPack repository to your root build.gradle at the end of repositories: 22 | ``` 23 | allprojects { 24 | repositories { 25 | ... 26 | maven { url 'https://jitpack.io' } 27 | } 28 | } 29 | ``` 30 | 2. Add the LiquidSwipe dependency in the build.gradle: 31 | ``` 32 | implementation 'com.github.Chrisvin:LiquidSwipe:1.3' 33 | ``` 34 | 35 | #### Use `LiquidSwipeViewPager` instead of the normal `ViewPager` 36 | ``` 37 | 41 | 42 | 46 | 47 | 48 | ``` 49 | 50 | #### Use a `LiquidSwipeLayout` as the base container in the fragment layouts 51 | ``` 52 | 53 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ``` 67 | Note : [Dokka generated documentation on LiquidSwipeLayouts](https://chrisvin.github.io/LiquidSwipe/com.jem.liquidswipe.layout/) 68 | 69 | ### And you're done, easy-peasy. ^_^ 70 | 71 | ## Touch Interactive - Making the LiquidSwipe wave center Y value match the touch Y value 72 | 73 | Rather than having the wave center Y value always be layout.height/2 , it would be more aesthetically pleasing for it to be the same as the touch Y value. 74 | The following code can be used to dynamically change the `waveCenterY` based on the touch position on the `LiquidSwipeViewPager`. 75 | (The reason this isn't done internally in the library is because the viewpager layouts don't get the touch events when said touch events are consumed directly by the viewpager) 76 | 77 | 1. In the `Activity`/`Fragment` class containing the `LiquidSwipeViewPager` 78 | ```kotlin 79 | // Create an array of LiquidSwipeCPP, one for each layout in the PagerAdapter 80 | val liquidSwipeClipPathProviders = Array(titleArray.count()) { 81 | LiquidSwipeClipPathProvider() 82 | } 83 | 84 | // Pass the LiquidSwipeCPP array to the adapter 85 | viewpager.adapter = CustomPagerAdapter(this, liquidSwipeClipPathProviders) 86 | // Similar logic can also be applied for your custom FragmentPagerAdapter/FragmentStatePagerAdapter 87 | 88 | // Listen to onTouch events on the viewpager and update the waveCenterY value of the LiquidSwipeCPPs 89 | viewpager.setOnTouchListener { _, event -> 90 | val waveCenterY = event.y 91 | liquidSwipeClipPathProviders.map { 92 | it.waveCenterY = waveCenterY 93 | } 94 | false 95 | } 96 | ``` 97 | 2. In the `PagerAdapter` 98 | ```kotlin 99 | // Set the layout's clipPathProvider to the corresponding `LiquidSwipeClipPathProvider` 100 | (layout as? LiquidSwipeLayout)?.clipPathProvider = liquidSwipeClipPathProviders[position] 101 | ``` 102 | 103 | The above code has been showcased in the demo app, feel free to look at it for reference. 104 | 105 | **Note**: 106 | This is not a perfect solution, in fact some artifacts might occur due to the quick waveCenterY value jumps. 107 | But for now, this is the cleanest solution I can think of. 108 | Anyone else with a better solution is welcome to fork and submit a pull request. :) 109 | 110 | 111 | 112 | ## Creating custom swipe animations 113 | 114 | The concept for the `ClipPathProvider` in LiquidSwipe is the same as that in the [EasyReveal library](https://github.com/Chrisvin/EasyReveal) (If you haven't already, then you should really check it out, infact the first version of LiquidSwipe used [EasyReveal](https://github.com/Chrisvin/EasyReveal) as a dependency). 115 | 116 | You can create your own swipe animation by extending the [ClipPathProvider](https://github.com/Chrisvin/LiquidSwipe/blob/master/liquidswipe/src/main/java/com/jem/liquidswipe/base/ClipPathProvider.kt) and implementing the `getPath()` method. `getPath()` provides the [Path](https://developer.android.com/reference/android/graphics/Path) for a given *percent* value on the provided *view*. The path gotten from `getPath()` is then used to clip the view using `canvas.clipPath(path, op)` (The `op` value is provided by the `ClipPathProvider` as well). You can then set your custom [ClipPathProvider](https://github.com/Chrisvin/LiquidSwipe/blob/master/liquidswipe/src/main/java/com/jem/liquidswipe/base/ClipPathProvider.kt) to your layouts. 117 | 118 | ## API Documentation 119 | 120 | Documentation generated using Dokka : [chrisvin.github.io/LiquidSwipe](https://chrisvin.github.io/LiquidSwipe/) 121 | 122 | ## Bugs and Feedback 123 | For bugs, questions and discussions please use the [Github Issues](https://github.com/Chrisvin/LiquidSwipe/issues). 124 | 125 | ## Credits 126 | 1. [Cuberto's liquid-swipe for iOS](https://github.com/Cuberto/liquid-swipe) - Source of inspiration 127 | 2. [Alvaro Fabre](https://lottiefiles.com/tomfabre) - Designer of the lottie animations in the demo app 128 | 129 | ## License 130 | ``` 131 | MIT License 132 | 133 | Copyright (c) 2019 Jem 134 | 135 | Permission is hereby granted, free of charge, to any person obtaining a copy 136 | of this software and associated documentation files (the "Software"), to deal 137 | in the Software without restriction, including without limitation the rights 138 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 139 | copies of the Software, and to permit persons to whom the Software is 140 | furnished to do so, subject to the following conditions: 141 | 142 | The above copyright notice and this permission notice shall be included in all 143 | copies or substantial portions of the Software. 144 | 145 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 146 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 147 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 148 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 149 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 150 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 151 | SOFTWARE. 152 | ``` 153 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion "29.0.2" 8 | 9 | defaultConfig { 10 | applicationId "com.example.liquidswipedemo" 11 | minSdkVersion 21 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 31 | implementation 'androidx.appcompat:appcompat:1.1.0' 32 | implementation 'androidx.core:core-ktx:1.1.0' 33 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 34 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 38 | 39 | implementation 'com.airbnb.android:lottie:3.2.0' 40 | 41 | implementation project(':liquidswipe') 42 | } 43 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/liquidswipedemo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.liquidswipedemo 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.example.liquidswipedemo", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/liquidswipedemo/CustomFragmentPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.liquidswipedemo 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentManager 5 | import androidx.fragment.app.FragmentStatePagerAdapter 6 | 7 | class CustomFragmentPagerAdapter(fm: FragmentManager) : 8 | FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { 9 | 10 | override fun getItem(position: Int): Fragment { 11 | return DummyFragment.newInstance( 12 | backgroundColorArray[(position % titleArray.count())], 13 | resourceArray[(position % titleArray.count())], 14 | titleArray[(position % titleArray.count())] 15 | ) 16 | } 17 | 18 | override fun getCount(): Int { 19 | return titleArray.count() * 20 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/liquidswipedemo/CustomPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.liquidswipedemo 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.viewpager.widget.PagerAdapter 9 | import com.airbnb.lottie.LottieAnimationView 10 | import com.airbnb.lottie.LottieDrawable 11 | import com.jem.liquidswipe.clippathprovider.LiquidSwipeClipPathProvider 12 | import com.jem.liquidswipe.base.LiquidSwipeLayout 13 | 14 | class CustomPagerAdapter( 15 | private val context: Context, 16 | private val liquidSwipeClipPathProviders: Array 17 | ) : PagerAdapter() { 18 | 19 | override fun instantiateItem(container: ViewGroup, position: Int): Any { 20 | val layout = LayoutInflater.from(context).inflate(R.layout.fragment_dummy, container, false) 21 | 22 | layout.setBackgroundColor(backgroundColorArray[(position % titleArray.count())]) 23 | 24 | layout.findViewById(R.id.lottieAnimationView).setAnimation( 25 | resourceArray[(position % titleArray.count())] 26 | ) 27 | layout.findViewById(R.id.lottieAnimationView).repeatCount = 28 | LottieDrawable.INFINITE 29 | layout.findViewById(R.id.lottieAnimationView).repeatMode = 30 | LottieDrawable.REVERSE 31 | layout.findViewById(R.id.lottieAnimationView).playAnimation() 32 | 33 | layout.findViewById(R.id.fragment_textview).text = 34 | titleArray[(position % titleArray.count())] 35 | 36 | (layout as? LiquidSwipeLayout)?.clipPathProvider = 37 | liquidSwipeClipPathProviders[(position % titleArray.count())] 38 | 39 | container.addView(layout) 40 | return layout 41 | } 42 | 43 | override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { 44 | container.removeView(`object` as View) 45 | } 46 | 47 | override fun isViewFromObject(view: View, `object`: Any): Boolean { 48 | return `object` == view 49 | } 50 | 51 | override fun getCount(): Int { 52 | return titleArray.count() * 20 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/liquidswipedemo/DataValues.kt: -------------------------------------------------------------------------------- 1 | package com.example.liquidswipedemo 2 | 3 | import android.graphics.Color 4 | 5 | val backgroundColorArray: ArrayList = arrayListOf( 6 | Color.parseColor("#6200EE"), 7 | Color.parseColor("#9FD29D"), 8 | Color.parseColor("#AFE1F0"), 9 | Color.parseColor("#F6D336"), 10 | Color.parseColor("#FA796B") 11 | ) 12 | val resourceArray: ArrayList = arrayListOf( 13 | R.raw.mountain, 14 | R.raw.map, 15 | R.raw.luggage, 16 | R.raw.camera, 17 | R.raw.van 18 | ) 19 | val titleArray: ArrayList = arrayListOf( 20 | "Hello fellow developer", 21 | "If you like this library", 22 | "Then do star it", 23 | "And check out my other libraries", 24 | "Cheers ^_^" 25 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/liquidswipedemo/DummyFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.liquidswipedemo 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.TextView 9 | import androidx.fragment.app.Fragment 10 | import com.airbnb.lottie.LottieAnimationView 11 | import com.airbnb.lottie.LottieDrawable 12 | 13 | private const val ARG_BACKGROUND_COLOR = "param1" 14 | private const val ARG_RESOURCE = "param2" 15 | private const val ARG_TITLE = "param3" 16 | 17 | class DummyFragment : Fragment() { 18 | private var param1: Int? = null 19 | private var param2: Int? = null 20 | private var param3: String? = null 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | arguments?.let { 25 | param1 = it.getInt(ARG_BACKGROUND_COLOR) 26 | param2 = it.getInt(ARG_RESOURCE) 27 | param3 = it.getString(ARG_TITLE) 28 | } 29 | } 30 | 31 | override fun onCreateView( 32 | inflater: LayoutInflater, container: ViewGroup?, 33 | savedInstanceState: Bundle? 34 | ): View? { 35 | // Inflate the layout for this fragment 36 | return inflater.inflate(R.layout.fragment_dummy, container, false).apply { 37 | setBackgroundColor(param1 ?: Color.RED) 38 | 39 | findViewById(R.id.lottieAnimationView).setAnimation( 40 | param2 ?: R.raw.mountain 41 | ) 42 | findViewById(R.id.lottieAnimationView).repeatCount = 43 | LottieDrawable.INFINITE 44 | findViewById(R.id.lottieAnimationView).repeatMode = 45 | LottieDrawable.REVERSE 46 | findViewById(R.id.lottieAnimationView).playAnimation() 47 | 48 | findViewById(R.id.fragment_textview).text = 49 | param3 ?: "Hello fellow developer!" 50 | } 51 | } 52 | 53 | companion object { 54 | @JvmStatic 55 | fun newInstance(param1: Int, param2: Int, param3: String) = 56 | DummyFragment().apply { 57 | arguments = Bundle().apply { 58 | putInt(ARG_BACKGROUND_COLOR, param1) 59 | putInt(ARG_RESOURCE, param2) 60 | putString(ARG_TITLE, param3) 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/liquidswipedemo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.liquidswipedemo 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.jem.liquidswipe.clippathprovider.LiquidSwipeClipPathProvider 6 | import kotlinx.android.synthetic.main.activity_main.* 7 | 8 | class MainActivity : AppCompatActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | setContentView(R.layout.activity_main) 13 | 14 | viewpager.adapter = CustomFragmentPagerAdapter(supportFragmentManager) 15 | 16 | /* 17 | // The following code can be used to dynamically change the waveCenterY 18 | // based on the touch position (on the LiquidSwipeViewPager). 19 | 20 | // The reason this isn't done internally is because, 21 | // sometimes the viewpager layouts don't get the touch events 22 | // when said touch events are consumed directly by the viewpager. 23 | 24 | // Create an array of LiquidSwipeCPP, one for each layout in the PagerAdapter 25 | val liquidSwipeClipPathProviders = Array(titleArray.count()) { 26 | LiquidSwipeClipPathProvider() 27 | } 28 | 29 | // Similar logic can also be applied for your custom FragmentPagerAdapter/FragmentStatePagerAdapter 30 | viewpager.adapter = CustomPagerAdapter(this, liquidSwipeClipPathProviders) 31 | 32 | // Listen to onTouch events on the viewpager and update the waveCenterY value of the LiquidSwipeCPPs 33 | viewpager.setOnTouchListener { _, event -> 34 | val waveCenterY = event.y 35 | liquidSwipeClipPathProviders.map { 36 | it.waveCenterY = waveCenterY 37 | } 38 | false 39 | } 40 | */ 41 | 42 | // Note that this is not a perfect solution, 43 | // in fact some artifacts might occur due to the quick waveCenterY value jumps. 44 | // But for now, this is the cleanest solution I can think of. 45 | // Anyone else with a better solution is welcome to fork and submit a pull request. :) 46 | 47 | // Create 20 times the number of actual pages, and start in the middle. 48 | // This way users can swipe left or right from the start. 49 | // Definitely not a good idea for production, but good enough for a demo app. 50 | viewpager.setCurrentItem(titleArray.count() * 10, false) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_dummy.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 19 | 31 | 32 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LiquidSwipeDemo 3 | 4 | 5 | Hello blank fragment 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/liquidswipedemo/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.liquidswipedemo 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.50' 5 | repositories { 6 | google() 7 | jcenter() 8 | maven { url 'https://jitpack.io' } 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.1' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | maven { url 'https://jitpack.io' } 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /demo screenshots/LiquidSwipeDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/demo screenshots/LiquidSwipeDemo.gif -------------------------------------------------------------------------------- /demo screenshots/LiquidSwipeDemo_Touch_Interactive.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/demo screenshots/LiquidSwipeDemo_Touch_Interactive.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Nov 16 16:42:20 IST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 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 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /liquidswipe/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | /* Dokka documentation plugin repositories & dependencies*/ 6 | buildscript { 7 | repositories { 8 | jcenter() 9 | } 10 | dependencies { 11 | classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.9.18" 12 | } 13 | } 14 | 15 | repositories { 16 | maven { url 'https://dl.bintray.com/kotlin/dokka' } 17 | } 18 | 19 | apply plugin: 'org.jetbrains.dokka' 20 | 21 | android { 22 | compileSdkVersion 29 23 | buildToolsVersion "29.0.2" 24 | 25 | defaultConfig { 26 | minSdkVersion 21 27 | targetSdkVersion 29 28 | versionCode 1 29 | versionName "1.0" 30 | 31 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 32 | consumerProguardFiles 'consumer-rules.pro' 33 | } 34 | 35 | buildTypes { 36 | release { 37 | minifyEnabled false 38 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 39 | } 40 | } 41 | 42 | dokka { 43 | outputFormat = 'gfm' 44 | outputDirectory = "$buildDir/javadoc" 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation fileTree(dir: 'libs', include: ['*.jar']) 50 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50" 51 | implementation 'androidx.appcompat:appcompat:1.1.0' 52 | implementation 'androidx.core:core-ktx:1.1.0' 53 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 54 | testImplementation 'junit:junit:4.12' 55 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 56 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 57 | } 58 | -------------------------------------------------------------------------------- /liquidswipe/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chrisvin/LiquidSwipe/ae50e891c2803fa864ef3f2a48cafc43ee0a3b8e/liquidswipe/consumer-rules.pro -------------------------------------------------------------------------------- /liquidswipe/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /liquidswipe/src/androidTest/java/com/jem/liquidswipe/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.jem.liquidswipe 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.jem.liquidswipe.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /liquidswipe/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /liquidswipe/src/main/java/com/jem/liquidswipe/LiquidSwipeViewPager.kt: -------------------------------------------------------------------------------- 1 | package com.jem.liquidswipe 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.view.animation.DecelerateInterpolator 7 | import androidx.viewpager.widget.ViewPager 8 | import com.jem.liquidswipe.base.LiquidSwipeLayout 9 | import com.jem.liquidswipe.util.FixedSpeedScroller 10 | import kotlin.math.abs 11 | 12 | /** 13 | * `LiquidSwipeViewPager` is a custom [ViewPager] that uses a fixed scroller and uses a [LiquidSwipePageTransformer] as it's page transformer. 14 | * 15 | * Note: Setting another page transformer to [LiquidSwipeViewPager] would remove the [LiquidSwipePageTransformer]. 16 | */ 17 | class LiquidSwipeViewPager : ViewPager { 18 | constructor(context: Context) : super(context) { 19 | initialize(context, null) 20 | } 21 | 22 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 23 | initialize(context, attrs) 24 | } 25 | 26 | private fun initialize(context: Context, attrs: AttributeSet?) { 27 | setPageTransformer(true, LiquidSwipePageTransformer()) 28 | 29 | var scrollerDuration = DEFAULT_SCROLLER_DURATION 30 | attrs?.let { 31 | val typedArray = 32 | context.obtainStyledAttributes(it, R.styleable.LiquidSwipeViewPager, 0, 0) 33 | typedArray.apply { 34 | scrollerDuration = getInt( 35 | R.styleable.LiquidSwipeViewPager_scrollerDuration, 36 | DEFAULT_SCROLLER_DURATION 37 | ) 38 | } 39 | } 40 | setDuration(scrollerDuration) 41 | } 42 | 43 | /** 44 | * Sets the fixed scroller duration for the [LiquidSwipeViewPager]. 45 | * @param duration Duration taken for viewpager to settle into position. 46 | */ 47 | public fun setDuration(duration: Int) { 48 | val mScroller = ViewPager::class.java.getDeclaredField("mScroller") 49 | mScroller.isAccessible = true 50 | val scroller = FixedSpeedScroller(context, DecelerateInterpolator()) 51 | scroller.scrollerDuration = duration 52 | mScroller.set(this, scroller) 53 | } 54 | 55 | companion object Constants { 56 | private const val DEFAULT_SCROLLER_DURATION = 1000 57 | } 58 | 59 | /** 60 | * `LiquidSwipePageTransformer` is a custom [ViewPager.PageTransformer] that is used for LiquidSwipe reveal. 61 | */ 62 | class LiquidSwipePageTransformer : ViewPager.PageTransformer { 63 | override fun transformPage(page: View, position: Float) { 64 | if (page is LiquidSwipeLayout) { 65 | when { 66 | position < -1 -> { 67 | page.revealForPercentage(0f) 68 | } 69 | position < 0 -> { 70 | page.translationX = -(page.width * position) 71 | page.revealForPercentage(100 - abs(position * 100)) 72 | } 73 | position <= 1 -> { 74 | page.revealForPercentage(100f) 75 | page.translationX = -(page.width * position) 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /liquidswipe/src/main/java/com/jem/liquidswipe/base/ClipPathProvider.kt: -------------------------------------------------------------------------------- 1 | package com.jem.liquidswipe.base 2 | 3 | import android.graphics.Path 4 | import android.graphics.Region 5 | import android.os.Build 6 | import android.view.View 7 | 8 | /** Abstract class that should be extended by other ClipPathProvider classes. */ 9 | abstract class ClipPathProvider { 10 | /** Path variable that should be used to make the path to be returned in [getPath] function.*/ 11 | protected var path: Path = Path() 12 | /** 13 | * Region.Op variable that is used in [android.graphics.Canvas.clipPath] in the EasyRevealLayouts 14 | * @throws IllegalArgumentException Will be thrown if values other than [Region.Op.INTERSECT] and 15 | * [Region.Op.DIFFERENCE] are provided when API level is greater than or equal to [Build.VERSION_CODES.P]. 16 | */ 17 | var op: Region.Op = Region.Op.INTERSECT 18 | set(value) { 19 | require(!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && op != Region.Op.INTERSECT && op != Region.Op.DIFFERENCE)) { "Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed" } 20 | field = value 21 | } 22 | 23 | /** 24 | * Generate the [Path] for the **percent** value to be applied in the **view**. 25 | * @param percent Reveal percentage 26 | * @param view View on which to apply clipPath for reveal 27 | * @return **path** that can be used to clip the view. 28 | */ 29 | abstract fun getPath(percent: Float, view: View): Path 30 | } -------------------------------------------------------------------------------- /liquidswipe/src/main/java/com/jem/liquidswipe/base/LiquidSwipeLayout.kt: -------------------------------------------------------------------------------- 1 | package com.jem.liquidswipe.base 2 | 3 | /** 4 | * LiquidSwipeLayout is the base interface for all the other LiquidSwipeLayouts 5 | */ 6 | interface LiquidSwipeLayout { 7 | /** ClipPathProvider provides the path used for clipping. */ 8 | var clipPathProvider: ClipPathProvider 9 | /** Percentage of the view currently revealed. */ 10 | var currentRevealPercent: Float 11 | 12 | /** 13 | * Update view to specified reveal percentage. 14 | * @param percent value should be between 0 and 100 (inclusive). 15 | */ 16 | fun revealForPercentage(percent: Float): Unit 17 | } -------------------------------------------------------------------------------- /liquidswipe/src/main/java/com/jem/liquidswipe/clippathprovider/LiquidSwipeClipPathProvider.kt: -------------------------------------------------------------------------------- 1 | package com.jem.liquidswipe.clippathprovider 2 | 3 | import android.graphics.Path 4 | import android.view.View 5 | import com.jem.liquidswipe.base.ClipPathProvider 6 | import kotlin.math.cos 7 | import kotlin.math.exp 8 | import kotlin.math.pow 9 | 10 | /** 11 | * [ClipPathProvider] which provides LiquidSwipe path. 12 | */ 13 | class LiquidSwipeClipPathProvider : ClipPathProvider() { 14 | /** Center of the wave */ 15 | var waveCenterY: Float = 0f 16 | /** Initial horizontal radius of the wave */ 17 | var initialHorizontalRadius = 0f 18 | /** Initial vertical radius of the wave */ 19 | var initialVerticalRadius = 82f 20 | /** Initial revealed side width */ 21 | var initialSideWidth = 0f 22 | 23 | private var waveHorizontalRadius: Float = 0f 24 | private var waveVerticalRadius: Float = 0f 25 | private var sideWidth: Float = 0f 26 | 27 | companion object Constants { 28 | private fun getMaxHorizontalRadius(view: View): Float { 29 | return view.width.toFloat() * 0.8f 30 | } 31 | 32 | private fun getMaxVerticalRadius(view: View): Float { 33 | return view.height.toFloat() * 0.9f 34 | } 35 | 36 | private fun getInitialWaveCenterY(view: View): Float { 37 | return view.height.toFloat() * 0.7167487685f 38 | } 39 | } 40 | 41 | override fun getPath(percent: Float, view: View): Path { 42 | waveCenterY = if (waveCenterY == 0f) view.height.toFloat() / 2 else waveCenterY 43 | waveHorizontalRadius = getWaveHorRadius(1 - (percent / 100), view) 44 | waveVerticalRadius = getWaveVertRadius(1 - (percent / 100), view) 45 | sideWidth = getSideWidth(1 - (percent / 100), view) 46 | 47 | path.reset() 48 | val maskWidth = view.width - sideWidth 49 | path.moveTo(maskWidth - sideWidth, 0f) 50 | path.lineTo(0f, 0f) 51 | path.lineTo(0f, view.height.toFloat()) 52 | path.lineTo(maskWidth, view.height.toFloat()) 53 | 54 | if (percent == 100f) { 55 | path.close() 56 | return path 57 | } 58 | 59 | val curveStartY = waveCenterY + waveVerticalRadius 60 | path.lineTo(maskWidth, curveStartY) 61 | 62 | path.cubicTo( 63 | maskWidth, 64 | (curveStartY - waveVerticalRadius * 0.1346194756).toFloat(), 65 | (maskWidth - waveHorizontalRadius * 0.05341339583).toFloat(), 66 | (curveStartY - waveVerticalRadius * 0.2412779634).toFloat(), 67 | (maskWidth - waveHorizontalRadius * 0.1561501458).toFloat(), 68 | (curveStartY - waveVerticalRadius * 0.3322374268).toFloat() 69 | ) 70 | path.cubicTo( 71 | (maskWidth - waveHorizontalRadius * 0.2361659167).toFloat(), 72 | (curveStartY - waveVerticalRadius * 0.4030805244).toFloat(), 73 | (maskWidth - waveHorizontalRadius * 0.3305285625).toFloat(), 74 | (curveStartY - waveVerticalRadius * 0.4561193293).toFloat(), 75 | (maskWidth - waveHorizontalRadius * 0.5012484792).toFloat(), 76 | (curveStartY - waveVerticalRadius * 0.5350576951).toFloat() 77 | ) 78 | path.cubicTo( 79 | (maskWidth - waveHorizontalRadius * 0.515878125).toFloat(), 80 | (curveStartY - waveVerticalRadius * 0.5418222317).toFloat(), 81 | (maskWidth - waveHorizontalRadius * 0.5664134792).toFloat(), 82 | (curveStartY - waveVerticalRadius * 0.5650349878).toFloat(), 83 | (maskWidth - waveHorizontalRadius * 0.574934875).toFloat(), 84 | (curveStartY - waveVerticalRadius * 0.5689655122).toFloat() 85 | ) 86 | path.cubicTo( 87 | (maskWidth - waveHorizontalRadius * 0.7283715208).toFloat(), 88 | (curveStartY - waveVerticalRadius * 0.6397387195).toFloat(), 89 | (maskWidth - waveHorizontalRadius * 0.8086618958).toFloat(), 90 | (curveStartY - waveVerticalRadius * 0.6833456585).toFloat(), 91 | (maskWidth - waveHorizontalRadius * 0.8774032292).toFloat(), 92 | (curveStartY - waveVerticalRadius * 0.7399037439).toFloat() 93 | ) 94 | path.cubicTo( 95 | (maskWidth - waveHorizontalRadius * 0.9653464583).toFloat(), 96 | (curveStartY - waveVerticalRadius * 0.8122605122).toFloat(), 97 | maskWidth - waveHorizontalRadius, 98 | (curveStartY - waveVerticalRadius * 0.8936183659).toFloat(), 99 | maskWidth - waveHorizontalRadius, curveStartY - waveVerticalRadius 100 | ) 101 | path.cubicTo( 102 | maskWidth - waveHorizontalRadius, 103 | (curveStartY - waveVerticalRadius * 1.100142878).toFloat(), 104 | (maskWidth - waveHorizontalRadius * 0.9595746667).toFloat(), 105 | (curveStartY - waveVerticalRadius * 1.1887991951).toFloat(), 106 | (maskWidth - waveHorizontalRadius * 0.8608411667).toFloat(), 107 | (curveStartY - waveVerticalRadius * 1.270484439).toFloat() 108 | ) 109 | path.cubicTo( 110 | (maskWidth - waveHorizontalRadius * 0.7852123333).toFloat(), 111 | (curveStartY - waveVerticalRadius * 1.3330544756).toFloat(), 112 | (maskWidth - waveHorizontalRadius * 0.703382125).toFloat(), 113 | (curveStartY - waveVerticalRadius * 1.3795848049).toFloat(), 114 | (maskWidth - waveHorizontalRadius * 0.5291125625).toFloat(), 115 | (curveStartY - waveVerticalRadius * 1.4665102805).toFloat() 116 | ) 117 | path.cubicTo( 118 | (maskWidth - waveHorizontalRadius * 0.5241858333).toFloat(), 119 | (curveStartY - waveVerticalRadius * 1.4689677195).toFloat(), 120 | (maskWidth - waveHorizontalRadius * 0.505739125).toFloat(), 121 | (curveStartY - waveVerticalRadius * 1.4781625854).toFloat(), 122 | (maskWidth - waveHorizontalRadius * 0.5015305417).toFloat(), 123 | (curveStartY - waveVerticalRadius * 1.4802616098).toFloat() 124 | ) 125 | path.cubicTo( 126 | (maskWidth - waveHorizontalRadius * 0.3187486042).toFloat(), 127 | (curveStartY - waveVerticalRadius * 1.5714239024).toFloat(), 128 | (maskWidth - waveHorizontalRadius * 0.2332057083).toFloat(), 129 | (curveStartY - waveVerticalRadius * 1.6204116463).toFloat(), 130 | (maskWidth - waveHorizontalRadius * 0.1541165417).toFloat(), 131 | (curveStartY - waveVerticalRadius * 1.687403).toFloat() 132 | ) 133 | path.cubicTo( 134 | (maskWidth - waveHorizontalRadius * 0.0509933125).toFloat(), 135 | (curveStartY - waveVerticalRadius * 1.774752061).toFloat(), 136 | maskWidth, (curveStartY - waveVerticalRadius * 1.8709256829).toFloat(), 137 | maskWidth, curveStartY - waveVerticalRadius * 2 138 | ) 139 | 140 | path.lineTo(maskWidth, 0f) 141 | path.close() 142 | return path 143 | } 144 | 145 | private fun getWaveHorRadius(percent: Float, view: View): Float { 146 | if (percent <= 0) { 147 | return initialHorizontalRadius 148 | } 149 | if (percent >= 1) { 150 | return 0f 151 | } 152 | val p1: Float = 0.4f 153 | if (percent <= p1) { 154 | return initialHorizontalRadius + percent / p1 * (getMaxHorizontalRadius(view) - initialHorizontalRadius) 155 | } 156 | val t: Float = ((percent - p1) / (1.0 - p1)).toFloat() 157 | val A: Float = getMaxHorizontalRadius(view) 158 | val r: Float = 40f 159 | val m: Float = 9.8f 160 | val beta: Float = r / (2 * m) 161 | val k: Float = 50f 162 | val omega0: Float = k / m 163 | val omega: Float = (-beta.pow(2) + omega0.pow(2)).pow(0.5f) 164 | 165 | return A * exp(-beta * t) * cos(omega * t) 166 | } 167 | 168 | private fun getWaveHorRadiusBack(percent: Float, view: View): Float { 169 | if (percent <= 0) { 170 | return initialHorizontalRadius 171 | } 172 | if (percent >= 1) { 173 | return 0f 174 | } 175 | val p1: Float = 0.4f 176 | if (percent <= p1) { 177 | return initialHorizontalRadius + percent / p1 * initialHorizontalRadius 178 | } 179 | val t: Float = ((percent - p1) / (1.0 - p1)).toFloat() 180 | val A: Float = 2 * initialHorizontalRadius 181 | val r: Float = 40f 182 | val m: Float = 9.8f 183 | val beta: Float = r / (2 * m) 184 | val k: Float = 50f 185 | val omega0: Float = k / m 186 | val omega: Float = (-beta.pow(2) + omega0.pow(2)).pow(0.5f) 187 | 188 | return A * exp(-beta * t) * cos(omega * t) 189 | } 190 | 191 | private fun getWaveVertRadius(percent: Float, view: View): Float { 192 | val p1: Float = 0.4f 193 | if (percent <= 0) { 194 | return initialVerticalRadius 195 | } 196 | if (percent >= p1) { 197 | return getMaxVerticalRadius(view) 198 | } 199 | return initialVerticalRadius + (getMaxVerticalRadius(view) - initialVerticalRadius) * percent / p1 200 | } 201 | 202 | private fun getSideWidth(percent: Float, view: View): Float { 203 | val p1: Float = 0.2f 204 | val p2: Float = 0.8f 205 | if (percent <= p1) { 206 | return initialSideWidth 207 | } 208 | if (percent >= p2) { 209 | return view.width.toFloat() 210 | } 211 | return initialSideWidth + (view.width - initialSideWidth) * ((percent - p1) / (p2 - p1)) 212 | } 213 | } -------------------------------------------------------------------------------- /liquidswipe/src/main/java/com/jem/liquidswipe/layout/LiquidSwipeConstraintLayout.kt: -------------------------------------------------------------------------------- 1 | package com.jem.liquidswipe.layout 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Path 6 | import android.util.AttributeSet 7 | import androidx.constraintlayout.widget.ConstraintLayout 8 | import com.jem.liquidswipe.base.ClipPathProvider 9 | import com.jem.liquidswipe.clippathprovider.LiquidSwipeClipPathProvider 10 | import com.jem.liquidswipe.base.LiquidSwipeLayout 11 | 12 | /** 13 | * `LiquidSwipeConstraintLayout` is a custom [ConstraintLayout] that implements [LiquidSwipeLayout]. 14 | */ 15 | open class LiquidSwipeConstraintLayout : ConstraintLayout, LiquidSwipeLayout { 16 | // Store path in local variable rather then getting it from ClipPathProvider each time 17 | private var path: Path? = null 18 | 19 | // Backing fields for LiquidSwipeLayout variables 20 | private var _clipPathProvider: ClipPathProvider = LiquidSwipeClipPathProvider() 21 | private var _currentRevealPercent: Float = 100f 22 | 23 | override var clipPathProvider = _clipPathProvider 24 | override var currentRevealPercent: Float 25 | get() = _currentRevealPercent 26 | set(value) { 27 | revealForPercentage(value) 28 | } 29 | 30 | constructor(context: Context?) : super(context) 31 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 32 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( 33 | context, 34 | attrs, 35 | defStyleAttr 36 | ) 37 | 38 | /** 39 | * Overriden from View 40 | */ 41 | override fun draw(canvas: Canvas?) { 42 | try { 43 | canvas?.save() 44 | path?.let { 45 | canvas?.clipPath(it, clipPathProvider.op) 46 | } 47 | super.draw(canvas) 48 | } finally { 49 | canvas?.restore() 50 | } 51 | } 52 | 53 | override fun revealForPercentage(percent: Float) { 54 | if (percent == _currentRevealPercent) return 55 | _currentRevealPercent = percent 56 | path = clipPathProvider.getPath(percent, this@LiquidSwipeConstraintLayout) 57 | invalidate() 58 | } 59 | } -------------------------------------------------------------------------------- /liquidswipe/src/main/java/com/jem/liquidswipe/layout/LiquidSwipeFrameLayout.kt: -------------------------------------------------------------------------------- 1 | package com.jem.liquidswipe.layout 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Path 6 | import android.os.Build 7 | import android.util.AttributeSet 8 | import android.widget.FrameLayout 9 | import androidx.annotation.RequiresApi 10 | import com.jem.liquidswipe.base.ClipPathProvider 11 | import com.jem.liquidswipe.clippathprovider.LiquidSwipeClipPathProvider 12 | import com.jem.liquidswipe.base.LiquidSwipeLayout 13 | 14 | /** 15 | * `LiquidSwipeFrameLayout` is a custom [FrameLayout] that implements [LiquidSwipeLayout]. 16 | */ 17 | open class LiquidSwipeFrameLayout : FrameLayout, LiquidSwipeLayout { 18 | // Store path in local variable rather then getting it from ClipPathProvider each time 19 | private var path: Path? = null 20 | 21 | // Backing fields for LiquidSwipeLayout variables 22 | private var _clipPathProvider: ClipPathProvider = LiquidSwipeClipPathProvider() 23 | private var _currentRevealPercent: Float = 100f 24 | 25 | override var clipPathProvider = _clipPathProvider 26 | override var currentRevealPercent: Float 27 | get() = _currentRevealPercent 28 | set(value) { 29 | revealForPercentage(value) 30 | } 31 | 32 | constructor(context: Context) : super(context) 33 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 34 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 35 | context, 36 | attrs, 37 | defStyleAttr 38 | ) 39 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 40 | constructor( 41 | context: Context, 42 | attrs: AttributeSet?, 43 | defStyleAttr: Int, 44 | defStyleRes: Int 45 | ) : super(context, attrs, defStyleAttr, defStyleRes) 46 | 47 | /** 48 | * Overriden from View 49 | */ 50 | override fun draw(canvas: Canvas?) { 51 | try { 52 | canvas?.save() 53 | path?.let { 54 | canvas?.clipPath(it, clipPathProvider.op) 55 | } 56 | super.draw(canvas) 57 | } finally { 58 | canvas?.restore() 59 | } 60 | } 61 | 62 | override fun revealForPercentage(percent: Float) { 63 | if (percent == _currentRevealPercent) return 64 | _currentRevealPercent = percent 65 | path = clipPathProvider.getPath(percent, this@LiquidSwipeFrameLayout) 66 | invalidate() 67 | } 68 | } -------------------------------------------------------------------------------- /liquidswipe/src/main/java/com/jem/liquidswipe/layout/LiquidSwipeLinearLayout.kt: -------------------------------------------------------------------------------- 1 | package com.jem.liquidswipe.layout 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Path 6 | import android.os.Build 7 | import android.util.AttributeSet 8 | import android.widget.LinearLayout 9 | import androidx.annotation.RequiresApi 10 | import com.jem.liquidswipe.base.ClipPathProvider 11 | import com.jem.liquidswipe.clippathprovider.LiquidSwipeClipPathProvider 12 | import com.jem.liquidswipe.base.LiquidSwipeLayout 13 | 14 | /** 15 | * `LiquidSwipeLinearLayout` is a custom [LinearLayout] that implements [LiquidSwipeLayout]. 16 | */ 17 | open class LiquidSwipeLinearLayout : LinearLayout, LiquidSwipeLayout { 18 | // Store path in local variable rather then getting it from ClipPathProvider each time 19 | private var path: Path? = null 20 | 21 | // Backing fields for LiquidSwipeLayout variables 22 | private var _clipPathProvider: ClipPathProvider = LiquidSwipeClipPathProvider() 23 | private var _currentRevealPercent: Float = 100f 24 | 25 | override var clipPathProvider = _clipPathProvider 26 | override var currentRevealPercent: Float 27 | get() = _currentRevealPercent 28 | set(value) { 29 | revealForPercentage(value) 30 | } 31 | 32 | constructor(context: Context?) : super(context) 33 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 34 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( 35 | context, 36 | attrs, 37 | defStyleAttr 38 | ) 39 | 40 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 41 | constructor( 42 | context: Context?, 43 | attrs: AttributeSet?, 44 | defStyleAttr: Int, 45 | defStyleRes: Int 46 | ) : super(context, attrs, defStyleAttr, defStyleRes) 47 | 48 | /** 49 | * Overriden from View 50 | */ 51 | override fun draw(canvas: Canvas?) { 52 | try { 53 | canvas?.save() 54 | path?.let { 55 | canvas?.clipPath(it, clipPathProvider.op) 56 | } 57 | super.draw(canvas) 58 | } finally { 59 | canvas?.restore() 60 | } 61 | } 62 | 63 | override fun revealForPercentage(percent: Float) { 64 | if (percent == _currentRevealPercent) return 65 | _currentRevealPercent = percent 66 | path = clipPathProvider.getPath(percent, this@LiquidSwipeLinearLayout) 67 | invalidate() 68 | } 69 | } -------------------------------------------------------------------------------- /liquidswipe/src/main/java/com/jem/liquidswipe/util/FixedSpeedScroller.kt: -------------------------------------------------------------------------------- 1 | package com.jem.liquidswipe.util 2 | 3 | import android.content.Context 4 | import android.view.animation.Interpolator 5 | import android.widget.Scroller 6 | 7 | internal class FixedSpeedScroller : Scroller { 8 | 9 | var scrollerDuration = 1000 10 | 11 | constructor(context: Context?, interpolator: Interpolator?) : super(context, interpolator) 12 | constructor(context: Context?, interpolator: Interpolator?, duration: Int) : this( 13 | context, 14 | interpolator 15 | ) { 16 | this.scrollerDuration = duration 17 | } 18 | 19 | constructor(context: Context?, interpolator: Interpolator?, flywheel: Boolean) : super( 20 | context, 21 | interpolator, 22 | flywheel 23 | ) 24 | 25 | override fun startScroll( 26 | startX: Int, 27 | startY: Int, 28 | dx: Int, 29 | dy: Int, 30 | duration: Int 31 | ) { // Ignore received duration, use fixed one instead 32 | super.startScroll(startX, startY, dx, dy, this.scrollerDuration) 33 | } 34 | 35 | override fun startScroll( 36 | startX: Int, 37 | startY: Int, 38 | dx: Int, 39 | dy: Int 40 | ) { // Ignore received duration, use fixed one instead 41 | super.startScroll(startX, startY, dx, dy, scrollerDuration) 42 | } 43 | } -------------------------------------------------------------------------------- /liquidswipe/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /liquidswipe/src/test/java/com/jem/liquidswipe/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.jem.liquidswipe 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='LiquidSwipeDemo' 2 | include ':app' 3 | include ':liquidswipe' 4 | --------------------------------------------------------------------------------