├── .gitignore ├── .idea ├── checkstyle-idea.xml ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── kotlinc.xml ├── misc.xml ├── modules.xml ├── qaplug_profiles.xml ├── runConfigurations.xml └── vcs.xml ├── .travis.yml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ebanx │ │ └── swipebutton │ │ ├── SwipeButtonInstrumentedTest.kt │ │ ├── SwipeButtonResultRobot.kt │ │ ├── SwipeButtonTestRobot.kt │ │ └── utils │ │ └── CenterSwipeAction.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── ebanx │ │ │ └── swipebutton │ │ │ ├── EbanxToggleButton.java │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_lock_open_black_24dp.png │ │ └── ic_lock_outline_black_24dp.png │ │ ├── drawable-xhdpi │ │ ├── ic_lock_open_black_24dp.png │ │ └── ic_lock_outline_black_24dp.png │ │ ├── drawable-xxhdpi │ │ ├── ic_lock_open_black_24dp.png │ │ └── ic_lock_outline_black_24dp.png │ │ ├── drawable │ │ ├── shape_button.xml │ │ ├── shape_button2.xml │ │ ├── shape_button3.xml │ │ ├── shape_button_squared.xml │ │ ├── shape_rounded.xml │ │ ├── shape_rounded2.xml │ │ └── shape_squared.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── content_main.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 │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── ebanx │ └── swipebutton │ └── ExampleUnitTest.java ├── build.gradle ├── gnag-build.sh ├── gradle.properties ├── gradle ├── buildWithTravis.sh └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── swipe-button ├── .gitignore ├── build.gradle ├── checkstyle.xml ├── pmd.xml ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml ├── java └── com │ └── ebanx │ └── swipebtn │ ├── DimentionUtils.java │ ├── OnActiveListener.java │ ├── OnStateChangeListener.java │ ├── SwipeButton.java │ └── TouchUtils.java └── res ├── drawable ├── shape_button.xml └── shape_rounded.xml └── values ├── attrs.xml └── strings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 36 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/qaplug_profiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 289 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk8 3 | sudo: false 4 | addons: 5 | apt: 6 | packages: 7 | - lynx 8 | android: 9 | components: 10 | - platform-tools 11 | - tools 12 | - build-tools-26.0.2 13 | - android-26 14 | - android-22 15 | - extra-android-m2repository 16 | - sys-img-armeabi-v7a-android-22 17 | before_install: chmod +x gradle/buildWithTravis.sh 18 | before_script: 19 | - android list targets 20 | - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a 21 | - emulator -avd test -no-audio -no-window & 22 | - android-wait-for-emulator 23 | - adb shell input keyevent 82 & 24 | 25 | script: 26 | - ./gradlew clean build connectedCheck 27 | after_failure: lynx -dump app/build/outputs/gnag/gnag.html 28 | after_success: 29 | - ./gradle/buildWithTravis.sh 30 | env: 31 | global: 32 | - secure: TZ11ozcJozynKr37pRPY2METtIEsDav//mE7wiCEumM4W59QMMnVqjJGTxBipbAuAX36PnEg0/jZbHfXjVJ4py9tGIDB3nlDULqRN/0Ddko/y4gzPQLVpvD22UU0N6g/r0tTJ4H775nxWF07Jw4nohtit7GVZS9Z/dgYvSFE7r2blnflzBBqqRZ2godE0r+8oZV6bRqpo/XxLEqiaBJS7cNbfZIKb+EnwPPuslA4ZdqPAM6U8rV43rzP4oi0M0Z+33s8D/ppDNhU6fPtt8cLIpJaMR5hLowg/U1y919Gp6wpA75Ttx9wZuxBFeAyJxKhJ0O5Myt21Soyw4blayUqqba3i/Nh+voNogwa9JUWzG/AOPDRQqCqKG0yswoRiKfTprSS6w487E1mHpLygftkaTAYKU3HYE/MZl8VYw6FUBC3QSNL5IJ/K8+k5K+Q9xwRBh9W57sclDcAq4fchrH4SKnYQ4GwFpLzyZBblL02ya+wi1kFxLlyHHPZNV/ZLAFG5uc/q1xfs3G6IPcjaRDWk2Ole0vTfqoCV1BVGKJ4+/M4XC+A6ML09CyOYzyu0mZFqWBzyCN3k3Up9sP+ysA9Et28ZO2JbCass3EV8hn1kF4eGwLgOtRpklZCttwLYPqmMjZQeFevF5+QRSZC80eHjf7kxQDwdBMja+dQS3xrIyE= 33 | - secure: LPWlPox2p8IiKQ8McuuK9e5jkuizp5A2m1nRjyGaq0FYyZD0rQ/AipiE6J25uyemxw11ZyJwno5pdrqytTQxDP2M4hVxMKU5vqvjVrVPDkSfTc0/xjW6efA+F2TBMzQavwANgS4y0OkKe18yZMPva/5sW9JbAuhbVDZn+rjRgoc7BSjX/xqw1QFmAmQXAfNxaUxpBmXSQbiZX67bFem9C77Ymhcd4bwvVu67ukVEQ+BhJw94y95j7enUBA9ioMQN8LIMH+/2U+YwHgQKaBYDQ1Y4PTxfOD8lis7z9m5ODXjCIEINJ2DXv4LM91oGtffQDftX+7P8J8oOF+p5AQWH85xgQh4bsuvRDJo0KXrUkTZ5knx76t+po9pxuLes3Mt0ykqAs5orpMBFPB6xYM7GWmzrGdIIHa4QdwmQyu8DyLCbRn3uCaIsEmxu6t+QOlEG9fEqog+TrRoPoM9ExzXQ9hyDAAqjhEVemhaqv5u8jjNtsV6s2MZyBEdH/BMbKEx9eBCsGCi5k++FPnIWB8ekUSsN8NUZvUXLZ3+7nEvLHjRBBonlJGuVuRjYpsOoCcaDvN3BVfDjA7+Ps7Y7Ci8SitzTYC/YLPNBp91oCjX7kDG13nOFgSiWZaUBrdekB5ush4TPxf4UOZuPHjadFin+c03iFn3cWKJo+Fd/66OLPV0= 34 | - secure: AHJQRYhNggAH/SSrIMjqTLZYyjc5BFQV8utlyF11sDmraWCs5IN0ZXc5CpKFQHwLgJjx5gObuHErXOSHDE5M9g/OM2OO5/hTnOSYC9xxNMmPtHf0J7rUnG/j4tICMWPjBsL/s7mZCxBMJ/AFNW2DGslIQDaSZJFENVvCehWZakDOV9pnZbYia47FJWwdW977OMKd/q3jOOSVMcSo6ZRNfjUyBe8gCW3oTJFh1N1EAhFog6xSnK45DoVW5cU6uWowVVsI39iUalmmUmCLm55w75IqmNTFcm7JHEdU/wbV034qI1GK3Oe6PSUQgC6JVBqw4ssjnp3qhAP6SeDA5UJNBcDplWMcqbIBVxfOp48QqWlgLEGp/uyr6KTYb+ZupKFVAEFXEgOUnNMXfJ51bwDBgFVdcy9n8QyIJn1tX5+Gj36C6kMBHIMgGraUZ+AT7ZMHfTrByD78Qk169/a379VsKPnf/ft6XzARXXbu87qJ0kHWbC7ugDeCfjjCUCLQ6rf3sdLj1TzK/qpvOj185wOyv+s0zUfFFuekXRux9itC0CDOgxw8XE0Li/Sp9SGfoN77m97bSy6+IHuM2RDKCleICC3fedwPPZVA0UdL6uL+bfBuHQK9/H6qV/xlJN+PDf6MbDCnsMsChp6VUBAHt/r1+K/ebB2nXzgdrDN9HCzZzuo= 35 | - secure: eC9/LKrCGhhP4xlePSHQ1K6XPhO4dNHwFfCDLeML/zzsOfyB9bENRfcoqrZ2GZ3UWigga8X8+lWVK2R+feRjNYrdR7oPXmvHwptSXS5+ECSkvAh5dusLIVKhDnsMsyXzSLV2eP8KMNGt6oBx28v4o9GtXTyLE9zIGQqeMEtwA0BRoqyE5MxEj6KWrwauNuKBfOQ/cdYN71KVwZ7VEA/2QhHvsfbNb9HEgzwy+mtPufOz6nucNfrR3Rwk5x+Kt9etOrdzWiX6rRtML7uN/IHXp2vbxHDgEaxmtrgQduqGRMacarvHiloU3QFIHvqszjGwspySJ/eZLPqELwmBBNGZmwUj6OrcDs9vG8gYHXneTXlNCLHJVLIEHxm+lYGejsPkytSUSNX1LtJnXuAaE9Z2BFudTx0pYKdWdMQZ/Ne2pUKTNZMyrMBfEazia9EeVd9C6DCqebXEjuM4w3y5kkigNhHvNKqH88GztcYXuxQ98saHIg9srHs6bavWp86sX+5FOWltTX3ZaQpMQJpAQoxI8S6gi/O1E17tqYmNWWW4Sh7yqB6IkgC6mciCD2UNCaLFiNeRirsK6M6TIun8oHXOTT1gG9ADPflflIU0xy/alCnC8T9hNiiGtsAmDMFdNQvvRQRwLpBb0MKE0gQASCjQZ0/18RWyZOO9bsUGuIq4IcA= 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [ ![Download](https://api.bintray.com/packages/ebanx/maven/swipe-button/images/download.svg) ](https://bintray.com/ebanx/maven/swipe-button/_latestVersion) [![Build Status](https://travis-ci.org/ebanx/swipe-button.svg?branch=master)](https://travis-ci.org/ebanx/swipe-button) 2 | 3 | # Swipe-Button 4 | 5 | ![enter image description here](https://lh3.googleusercontent.com/ylwD3k8NBLKtdv8--tSwQj0_k-PWdAX_MoWJf3lP80_zfU_qgr8trhRRGeOePwJVOUaXYfbRNlpoeZSD5f3LuDmK1AvI3_qaY5clFd7geffWsmkk2vWMg9NYvkp-G-6qkKhmlgTW4yWfz9BhOoLG74BnY9bQg2Lpxxt_uZTcUDO-48v4Fn0ZNVHGjpzzX0Q1ZsvRWjjuutYUzSyvDGXq7Vxkivrt6prV5amgMhtiUWmqHxKqMl5Ac6KWbV_NK7ZCawrivMda3KG3w83bU4ZvQ2h4MLv-QXkOJnpBS8A9c9QcATYflVhH7u5GqQUpiyG4XInHMzUrs0-WfFFCPEdIKT4z_gkyJpWR38DG5uVFAiVVL-MijtOANqBUbtRmH-KnlElvvPpkrHv5_KiPo5sgsUQw4LZAEXu_aCurBplMeq4vuKvlM04Si_0lI5FJCjPxWC_Yc1YWwbsnWja-ByHxI-oVBzTVJJLrEdtLBWhXwIsJ2N2-ig9Dq1z00h-uzpS6ayJB6i_m14eFO3-5NLXgU5RoalmZGBBEYALqPaOEnBiBqa7PPotj0ChWkwGa7HqfV4bgHmKO3lF76JAyVYq7Sv_T1ssrF2n4QDReMmrfA2HEOiUnXPn0GUqJ9HjmJUXcARsuZNM2OQNnxk_mb5PdHBlFrer_Op9sWtPrhyBqlFg3vg=w640-h177-no "swipe_one_state_with_trail.gif") 6 | 7 | Library of an android button activated by swipe. 8 | 9 | - Easy to use. 10 | - Makes your app look great 11 | - Better UX in sensitive button 12 | 13 | 14 | ## Installation 15 | 16 | compile 'com.ebanx:swipe-button:[latestVersion]' 17 | 18 | ## How to use 19 | 20 | Add the button in your layout file and customize it the way you like it. 21 | 22 | 46 | 47 | ### Setting the sliding button size 48 | You can set the size of the moving part of the button by changing the app:button_image_width and app:button_image_height properties. 49 | 50 | ### Setting the text part size 51 | You can set the size of the fixed part of the button by setting the text size of the setting the padding in this part. 52 | 53 | ### Listening for changes 54 | You can set a listener for state changes 55 | 56 | SwipeButton enableButton = (SwipeButton) findViewById(R.id.swipe_btn); 57 | enableButton.setOnStateChangeListener(new OnStateChangeListener() { 58 | @Override 59 | public void onStateChange(boolean active) { 60 | Toast.makeText(MainActivity.this, "State: " + active, Toast.LENGTH_SHORT).show(); 61 | } 62 | }); 63 | 64 | Or listen for the activation of the button 65 | 66 | swipeButtonNoState.setOnActiveListener(new OnActiveListener() { 67 | @Override 68 | public void onActive() { 69 | Toast.makeText(MainActivity.this, "Active!", Toast.LENGTH_SHORT).show(); 70 | } 71 | }); 72 | 73 | ## Configure XML 74 | 75 | - button_image_width: Change the width of the moving part of the button 76 | - button_image_height: Change the height of the moving part of the button 77 | - inner_text: Text in the center of the button. It disapears when swiped 78 | - inner_text_color: Color of the text 79 | - inner_text_size: Size of the text 80 | - inner_text_[direction]_padding: Sets the padding of the text inside the button. You can set how big this part of the button will by setting text size and padding. 81 | - button_image_disabled: Icon of the button when disabled. This is the initial state. 82 | - button_image_enabled: Icon of the button when disabled. This is the initial expanded state. 83 | - button_[direction]_padding: Sets the padding of the button the slide with the touch. You can set how big the button will be by setting the image and the padding 84 | - initial_state: Initial state. Default state is disabled. 85 | - has_activate_state: Set if the button stops in the "active" state. If false, the button will only come back to the initial state after swiped until the end of its way. Use OnActiveListener if you set the parameter to false. 86 | - button_trail_enabled: Set trailing effect enabled. 87 | - button_trail_drawable: Set the color of the trailing effect. 88 | 89 | ## CodePen 90 | If you would like to see a front-end version of this button you can check a codepen in this link: 91 | 92 | - http://codepen.io/gpressutto5/pen/NjJobG 93 | - https://codepen.io/issamu/pen/NgPOKb (with Safari fix) 94 | 95 | ## Bugs and features 96 | For bugs, feature requests, and discussion please use [GitHub Issues](https://github.com/ebanx/swipe-button/issues). 97 | 98 | ## Credits 99 | 100 | - Design: [Diego Martins](https://dribbble.com/diegomartins) 101 | - Development: [Leandro Borges Ferreira](https://github.com/leandroBorgesFerreira), [Vinicius Nadin](https://github.com/viniciato), [Rahul Kumar](https://github.com/rahulk11) 102 | - Codepen: [Guilherme Pressuto](https://github.com/gpressutto5) 103 | - Codepen: [João Issamu Francisco](https://github.com/joaoissamu) 104 | 105 | ### And that's it! Enjoy! 106 | 107 | > Written with [StackEdit](https://stackedit.io/). 108 | 109 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /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 26 7 | buildToolsVersion "26.0.2" 8 | defaultConfig { 9 | applicationId "com.ebanx.swipebutton" 10 | minSdkVersion 19 11 | targetSdkVersion 26 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | 23 | lintOptions { 24 | abortOnError false 25 | 26 | // Bug in the android sdk https://github.com/square/okio/issues/58 27 | // remove this on android O (it will include the missing packages). 28 | warning 'InvalidPackage' 29 | } 30 | 31 | } 32 | 33 | dependencies { 34 | def supportVersion = "26.1.0" 35 | 36 | compile fileTree(include: ['*.jar'], dir: 'libs') 37 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 38 | exclude group: 'com.android.support', module: 'support-annotations' 39 | }) 40 | 41 | compile "com.android.support:appcompat-v7:$supportVersion" 42 | compile "com.android.support:design:$supportVersion" 43 | testCompile 'junit:junit:4.12' 44 | compile project(':swipe-button') 45 | // compile 'com.ebanx:swipe-button:0.7.0' 46 | compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 47 | 48 | } 49 | repositories { 50 | mavenCentral() 51 | } 52 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/leandroferreira/Library/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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ebanx/swipebutton/SwipeButtonInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.ebanx.swipebutton 2 | 3 | import android.app.KeyguardManager 4 | import android.content.Context 5 | import android.support.test.rule.ActivityTestRule 6 | import android.view.WindowManager 7 | import com.ebanx.swipebtn.SwipeButton 8 | import kotlinx.android.synthetic.main.content_main.* 9 | import org.junit.Before 10 | import org.junit.Rule 11 | import org.junit.Test 12 | 13 | class SwipeButtonInstrumentedTest { 14 | 15 | @Rule 16 | @JvmField 17 | val activityTest = ActivityTestRule(MainActivity::class.java) 18 | 19 | @Suppress("DEPRECATION") 20 | @Before 21 | fun setUp() { 22 | // activityTest.activity.runOnUiThread { 23 | // val mKG = activityTest.activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager 24 | // val mLock = mKG.newKeyguardLock(Context.KEYGUARD_SERVICE) 25 | // mLock.disableKeyguard() 26 | // 27 | // //turn the screen on 28 | // activityTest.activity.window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or 29 | // WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or 30 | // WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or 31 | // WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or 32 | // WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) 33 | // } 34 | } 35 | 36 | @Test 37 | fun shouldSetEnabledAfterSwipe() { 38 | test { 39 | enableButton() 40 | } result { 41 | checkButtonIsEnabled(activityTest.activity.swipeBtnDisabled as SwipeButton) 42 | } 43 | } 44 | 45 | @Test 46 | fun shouldSetDisabledAfterClick() { 47 | test { 48 | enableButton() 49 | disableButton() 50 | } result { 51 | checkButtonIsDisabled(activityTest.activity.swipeBtnDisabled as SwipeButton) 52 | } 53 | } 54 | 55 | @Test 56 | fun shouldCreateButtonCorrectly() { 57 | test { } result { 58 | checkButtonIsDisabled(activityTest.activity.swipeBtnDisabled as SwipeButton) 59 | checkButtonIsEnabled(activityTest.activity.swipeBtnEnabled as SwipeButton) 60 | shouldBeginWithRightText("SWIPE") 61 | } 62 | } 63 | 64 | @Test 65 | fun shouldNotAcceptSwipeFromOutsideMovingPart() { 66 | test { 67 | enableFromMidOfButton() 68 | } result { 69 | checkButtonIsDisabled(activityTest.activity.swipeBtnDisabled as SwipeButton) 70 | } 71 | } 72 | 73 | fun test(func: SwipeButtonTestRobot.() -> Unit) = SwipeButtonTestRobot().apply { func() } 74 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ebanx/swipebutton/SwipeButtonResultRobot.kt: -------------------------------------------------------------------------------- 1 | package com.ebanx.swipebutton 2 | 3 | import android.support.test.espresso.Espresso.onView 4 | import android.support.test.espresso.assertion.ViewAssertions.matches 5 | import android.support.test.espresso.matcher.ViewMatchers.* 6 | import com.ebanx.swipebtn.SwipeButton 7 | import junit.framework.Assert 8 | import org.hamcrest.Matchers.allOf 9 | 10 | /** 11 | * Created by vinicius on 13/06/17. 12 | */ 13 | class SwipeButtonResultRobot { 14 | 15 | fun checkButtonIsEnabled(swipeButton: SwipeButton) { 16 | Assert.assertTrue(swipeButton.isActive) 17 | } 18 | 19 | fun checkButtonIsDisabled(swipeButton: SwipeButton) { 20 | Assert.assertFalse(swipeButton.isActive) 21 | } 22 | 23 | fun shouldBeginWithRightText(title: String) { 24 | onView(withText(title)).check(matches(isDisplayed())) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ebanx/swipebutton/SwipeButtonTestRobot.kt: -------------------------------------------------------------------------------- 1 | package com.ebanx.swipebutton 2 | 3 | import android.support.test.espresso.Espresso.onView 4 | import android.support.test.espresso.ViewAction 5 | import android.support.test.espresso.action.CoordinatesProvider 6 | import android.support.test.espresso.action.GeneralLocation 7 | import android.support.test.espresso.action.Press 8 | import android.support.test.espresso.action.Swipe 9 | import android.support.test.espresso.action.ViewActions.click 10 | import android.support.test.espresso.action.ViewActions.swipeRight 11 | import android.support.test.espresso.matcher.ViewMatchers.withId 12 | import ebanx.com.ego.utils.CenterSwipeAction 13 | 14 | /** 15 | * Created by vinicius on 13/06/17. 16 | */ 17 | class SwipeButtonTestRobot { 18 | 19 | fun enableButton() : SwipeButtonTestRobot { 20 | onView(withId(R.id.swipeBtnDisabled)).perform(swipeRight()) 21 | return this 22 | } 23 | 24 | fun disableButton() : SwipeButtonTestRobot { 25 | onView(withId(R.id.swipeBtnDisabled)).perform(click()) 26 | return this 27 | } 28 | 29 | fun enableFromMidOfButton() : SwipeButtonTestRobot { 30 | onView(withId(R.id.swipeBtnDisabled)).perform(swipeFromCenterToRight()) 31 | return this 32 | } 33 | 34 | private fun swipeFromCenterToRight(): ViewAction { 35 | val endCoordProvide = CoordinatesProvider { view -> 36 | val coordinates = GeneralLocation.CENTER.calculateCoordinates(view) 37 | coordinates[0] = 1500f 38 | coordinates 39 | } 40 | return CenterSwipeAction(Swipe.FAST, 41 | GeneralLocation.CENTER, 42 | endCoordProvide, 43 | Press.FINGER) 44 | } 45 | 46 | infix fun result(func: SwipeButtonResultRobot.() -> Unit) = SwipeButtonResultRobot().apply { func() } 47 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ebanx/swipebutton/utils/CenterSwipeAction.kt: -------------------------------------------------------------------------------- 1 | package ebanx.com.ego.utils 2 | 3 | import android.support.test.espresso.PerformException 4 | import android.support.test.espresso.UiController 5 | import android.support.test.espresso.ViewAction 6 | import android.support.test.espresso.action.CoordinatesProvider 7 | import android.support.test.espresso.action.PrecisionDescriber 8 | import android.support.test.espresso.action.Swiper 9 | import android.support.test.espresso.matcher.ViewMatchers 10 | import android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility 11 | import android.support.test.espresso.util.HumanReadables 12 | import android.view.View 13 | import android.view.ViewConfiguration 14 | import org.hamcrest.Matcher 15 | 16 | class CenterSwipeAction(private val swiper: Swiper, 17 | private val startCoordProvide: CoordinatesProvider, 18 | private val endCoordProvide: CoordinatesProvider, 19 | private val precDesc: PrecisionDescriber) : ViewAction { 20 | 21 | override fun getConstraints(): Matcher { 22 | return withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE) 23 | } 24 | 25 | override fun getDescription(): String { 26 | return "swipe from middle of screen" 27 | } 28 | 29 | @Suppress("CatchRuntimeException") 30 | override fun perform(uiController: UiController, view: View) { 31 | val startCoord = startCoordProvide.calculateCoordinates(view) 32 | val finalCoord = endCoordProvide.calculateCoordinates(view) 33 | val precision = precDesc.describePrecision() 34 | 35 | // you could try this for several times until Swiper.Status is achieved or try count is reached 36 | try { 37 | swiper.sendSwipe(uiController, startCoord, finalCoord, precision) 38 | } catch (re: RuntimeException) { 39 | throw PerformException.Builder() 40 | .withActionDescription(this.description) 41 | .withViewDescription(HumanReadables.describe(view)) 42 | .withCause(re) 43 | .build() 44 | } 45 | 46 | // ensures that the swipe has been run. 47 | uiController.loopMainThreadForAtLeast(ViewConfiguration.getPressedStateDuration().toLong()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebanx/swipebutton/EbanxToggleButton.java: -------------------------------------------------------------------------------- 1 | package com.ebanx.swipebutton; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.graphics.Canvas; 8 | import android.support.v4.content.ContextCompat; 9 | import android.util.AttributeSet; 10 | import android.view.Gravity; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.animation.AccelerateDecelerateInterpolator; 14 | import android.widget.Button; 15 | import android.widget.RelativeLayout; 16 | 17 | /** 18 | * Created by leandroferreira on 07/03/17. 19 | */ 20 | 21 | public class EbanxToggleButton extends RelativeLayout { 22 | 23 | private boolean isClicked; 24 | private Button mSwipeButton; 25 | private int initialButtonWidth; 26 | private int animationButtonWidth; 27 | 28 | public EbanxToggleButton(Context context) { 29 | super(context); 30 | 31 | init(context); 32 | } 33 | 34 | public EbanxToggleButton(Context context, AttributeSet attrs) { 35 | super(context, attrs); 36 | 37 | init(context); 38 | } 39 | 40 | public EbanxToggleButton(Context context, AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | 43 | init(context); 44 | } 45 | 46 | public EbanxToggleButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 47 | super(context, attrs, defStyleAttr, defStyleRes); 48 | 49 | init(context); 50 | } 51 | 52 | private void init(Context context){ 53 | isClicked = false; 54 | 55 | mSwipeButton = new Button(context); 56 | mSwipeButton.setText(null); 57 | mSwipeButton.setBackground(ContextCompat.getDrawable(context, R.drawable.shape_button)); 58 | 59 | LayoutParams layoutParamsButton = new LayoutParams( 60 | ViewGroup.LayoutParams.WRAP_CONTENT, 61 | ViewGroup.LayoutParams.MATCH_PARENT); 62 | 63 | layoutParamsButton.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE); 64 | layoutParamsButton.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); 65 | 66 | 67 | 68 | addView(mSwipeButton, layoutParamsButton); 69 | 70 | 71 | OnClickListener clickListener = new OnClickListener() { 72 | @Override 73 | public void onClick(View v) { 74 | if(!isClicked){ 75 | animateCheck(); 76 | isClicked = true; 77 | } else{ 78 | animateUncheck(); 79 | isClicked = false; 80 | } 81 | } 82 | }; 83 | 84 | setOnClickListener(clickListener); 85 | mSwipeButton.setOnClickListener(clickListener); 86 | 87 | } 88 | 89 | @Override 90 | public void onDraw(Canvas canvas){ 91 | super.onDraw(canvas); 92 | 93 | initialButtonWidth = mSwipeButton.getWidth(); 94 | animationButtonWidth = getWidth(); 95 | } 96 | 97 | private void animateCheck(){ 98 | final ValueAnimator expandAnimator = ValueAnimator.ofInt(initialButtonWidth, animationButtonWidth); 99 | expandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 100 | @Override 101 | public void onAnimationUpdate(ValueAnimator animation) { 102 | mSwipeButton.setWidth((Integer) animation.getAnimatedValue()); 103 | } 104 | }); 105 | 106 | expandAnimator.addListener(new Animator.AnimatorListener() { 107 | @Override 108 | public void onAnimationStart(Animator animation) { 109 | setGravity(Gravity.LEFT); 110 | } 111 | 112 | @Override 113 | public void onAnimationEnd(Animator animation) { 114 | 115 | } 116 | 117 | @Override 118 | public void onAnimationCancel(Animator animation) { 119 | 120 | } 121 | 122 | @Override 123 | public void onAnimationRepeat(Animator animation) { 124 | 125 | } 126 | }); 127 | 128 | final ValueAnimator shirinkAnimator = ValueAnimator.ofInt(animationButtonWidth, initialButtonWidth); 129 | shirinkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 130 | @Override 131 | public void onAnimationUpdate(ValueAnimator animation) { 132 | mSwipeButton.setWidth((Integer) animation.getAnimatedValue()); 133 | } 134 | }); 135 | shirinkAnimator.addListener(new Animator.AnimatorListener() { 136 | @Override 137 | public void onAnimationStart(Animator animation) { 138 | setGravity(Gravity.RIGHT); 139 | } 140 | 141 | @Override 142 | public void onAnimationEnd(Animator animation) { 143 | } 144 | 145 | @Override 146 | public void onAnimationCancel(Animator animation) { 147 | 148 | } 149 | 150 | @Override 151 | public void onAnimationRepeat(Animator animation) { 152 | 153 | } 154 | }); 155 | 156 | final AnimatorSet animatorSet = new AnimatorSet(); 157 | animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); 158 | animatorSet.playSequentially(expandAnimator, shirinkAnimator); 159 | 160 | animatorSet.addListener(getClickableListener()); 161 | 162 | animatorSet.start(); 163 | } 164 | 165 | private void animateUncheck(){ 166 | final ValueAnimator expandAnimator = ValueAnimator.ofInt(initialButtonWidth, animationButtonWidth); 167 | expandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 168 | @Override 169 | public void onAnimationUpdate(ValueAnimator animation) { 170 | mSwipeButton.setWidth((Integer) animation.getAnimatedValue()); 171 | } 172 | }); 173 | 174 | expandAnimator.addListener(new Animator.AnimatorListener() { 175 | @Override 176 | public void onAnimationStart(Animator animation) { 177 | setGravity(Gravity.RIGHT); 178 | } 179 | 180 | @Override 181 | public void onAnimationEnd(Animator animation) { 182 | } 183 | 184 | @Override 185 | public void onAnimationCancel(Animator animation) { 186 | 187 | } 188 | 189 | @Override 190 | public void onAnimationRepeat(Animator animation) { 191 | 192 | } 193 | }); 194 | 195 | final ValueAnimator shirinkAnimator = ValueAnimator.ofInt(animationButtonWidth, initialButtonWidth); 196 | shirinkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 197 | @Override 198 | public void onAnimationUpdate(ValueAnimator animation) { 199 | mSwipeButton.setWidth((Integer) animation.getAnimatedValue()); 200 | } 201 | }); 202 | 203 | 204 | 205 | shirinkAnimator.addListener(new Animator.AnimatorListener() { 206 | @Override 207 | public void onAnimationStart(Animator animation) { 208 | setGravity(Gravity.LEFT); 209 | } 210 | 211 | @Override 212 | public void onAnimationEnd(Animator animation) { 213 | } 214 | 215 | @Override 216 | public void onAnimationCancel(Animator animation) { 217 | 218 | } 219 | 220 | @Override 221 | public void onAnimationRepeat(Animator animation) { 222 | 223 | } 224 | }); 225 | 226 | final AnimatorSet animatorSet = new AnimatorSet(); 227 | animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); 228 | animatorSet.playSequentially(expandAnimator, shirinkAnimator); 229 | 230 | animatorSet.addListener(getClickableListener()); 231 | 232 | animatorSet.start(); 233 | } 234 | 235 | private Animator.AnimatorListener getClickableListener(){ 236 | return new Animator.AnimatorListener() { 237 | @Override 238 | public void onAnimationStart(Animator animation) { 239 | setClickable(false); 240 | mSwipeButton.setClickable(false); 241 | } 242 | 243 | @Override 244 | public void onAnimationEnd(Animator animation) { 245 | setClickable(true); 246 | mSwipeButton.setClickable(true); 247 | } 248 | 249 | @Override 250 | public void onAnimationCancel(Animator animation) {} 251 | 252 | @Override 253 | public void onAnimationRepeat(Animator animation) {} 254 | }; 255 | } 256 | 257 | } 258 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebanx/swipebutton/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.ebanx.swipebutton 2 | 3 | import android.os.Bundle 4 | import android.support.v4.content.ContextCompat 5 | import android.support.v7.app.AppCompatActivity 6 | import android.widget.Toast 7 | import kotlinx.android.synthetic.main.content_main.* 8 | 9 | class MainActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_main) 14 | 15 | swipeBtnEnabled.background = ContextCompat.getDrawable(this, R.drawable.shape_button2) 16 | swipeBtnEnabled.setSlidingButtonBackground(ContextCompat.getDrawable(this, R.drawable.shape_rounded2)) 17 | 18 | swipeBtnEnabled.setOnStateChangeListener { active -> 19 | Toast.makeText(this@MainActivity, "State: " + active, Toast.LENGTH_SHORT).show() 20 | if (active) { 21 | swipeBtnEnabled.setButtonBackground(ContextCompat.getDrawable(this@MainActivity, R.drawable.shape_button)) 22 | } else { 23 | swipeBtnEnabled.setButtonBackground(ContextCompat.getDrawable(this@MainActivity, R.drawable.shape_button3)) 24 | } 25 | } 26 | 27 | // swipeBtnDisabled.setDisabledStateNotAnimated() 28 | swipeBtnEnabled.setEnabledStateNotAnimated() 29 | 30 | swipeNoState.setOnActiveListener { Toast.makeText(this@MainActivity, "Active!", Toast.LENGTH_SHORT).show() } 31 | 32 | toggleBtn.setOnClickListener { 33 | if (!swipeBtnEnabled.isActive) { 34 | swipeBtnEnabled.toggleState() 35 | } 36 | } 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_lock_open_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebanx/swipe-button/ae46883b10cb9f92ebb0cf4b9a627f6e177153cb/app/src/main/res/drawable-hdpi/ic_lock_open_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_lock_outline_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebanx/swipe-button/ae46883b10cb9f92ebb0cf4b9a627f6e177153cb/app/src/main/res/drawable-hdpi/ic_lock_outline_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_lock_open_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebanx/swipe-button/ae46883b10cb9f92ebb0cf4b9a627f6e177153cb/app/src/main/res/drawable-xhdpi/ic_lock_open_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_lock_outline_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebanx/swipe-button/ae46883b10cb9f92ebb0cf4b9a627f6e177153cb/app/src/main/res/drawable-xhdpi/ic_lock_outline_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_lock_open_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebanx/swipe-button/ae46883b10cb9f92ebb0cf4b9a627f6e177153cb/app/src/main/res/drawable-xxhdpi/ic_lock_open_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_lock_outline_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebanx/swipe-button/ae46883b10cb9f92ebb0cf4b9a627f6e177153cb/app/src/main/res/drawable-xxhdpi/ic_lock_outline_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_button2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_button3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_button_squared.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_rounded2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_squared.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 36 | 37 | 60 | 61 | 62 | 63 | 84 | 85 |