├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ ├── com │ │ └── lorentzos │ │ │ └── flingswipe │ │ │ ├── BaseFlingAdapterView.java │ │ │ ├── FlingCardListener.java │ │ │ ├── LinearRegression.java │ │ │ └── SwipeFlingAdapterView.java │ └── me │ │ └── payge │ │ └── swipecardview │ │ └── MainActivity.java │ └── res │ ├── drawable-xxhdpi │ ├── default_card.png │ ├── home01_bg_card.9.png │ ├── home01_btn_collect.png │ ├── home01_icon_edu.png │ ├── home01_icon_location.png │ ├── home01_icon_work_year.png │ ├── i1.jpg │ ├── i2.jpg │ ├── i3.jpg │ ├── i4.jpg │ ├── i5.jpg │ └── i6.jpg │ ├── drawable │ └── bg_button.xml │ ├── layout │ ├── activity_main.xml │ └── card_new_item.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ ├── styles.xml │ └── swipe_fling_view.xml ├── build.gradle ├── ezgif.com.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwipeCardView 2 | SwipeCardView是基于Diolor的[Swipecards](https://github.com/Diolor/Swipecards)控件改进实现,SwipeCardView是一个优雅的刷脸控件,滑动刷脸伴随渐变层叠动画,带来前所未有的滑动刷脸体验。   3 | 4 | thanks Diolor [Swipecards](https://github.com/Diolor/Swipecards) 5 | 6 | ## Screenshot 7 | ![screen](https://github.com/xiepeijie/SwipeCardView/blob/master/ezgif.com.gif) 8 | 9 | ## Relative Project 10 | [SwipeAdapterView](https://github.com/xiepeijie/SwipeAdapterView) 11 | 12 | ## Usage 13 | ### XML: 14 | ``` 15 | 22 | ``` 23 | ### Java Code: 24 | ``` 25 | swipeView = (SwipeFlingAdapterView) findViewById(R.id.swipe_view); 26 | swipeView.setIsNeedSwipe(true);// 是否开启swipe滑动效果,当不调用此方法设置时,默认开启。 27 | swipeView.setFlingListener(this); 28 | swipeView.setOnItemClickListener(this); 29 | ``` 30 | **onFlingListener** 31 | ``` 32 | @Override 33 | public void removeFirstObjectInAdapter() { 34 | adapter.remove(0); 35 | } 36 | 37 | @Override 38 | public void onLeftCardExit(Object dataObject) { 39 | // to do something 40 | } 41 | 42 | @Override 43 | public void onRightCardExit(Object dataObject) { 44 | // to do something 45 | } 46 | 47 | @Override 48 | public void onAdapterAboutToEmpty(int itemsInAdapter) { 49 | if (itemsInAdapter == 3) { 50 | loadData(); 51 | } 52 | } 53 | ``` 54 | **Click to swipe** 55 | 56 | ``` 57 | @Override 58 | public void onClick(View v) { 59 | // swipe left 60 | swipeView.swipeLeft(); 61 | // swipe right 62 | //swipeView.swipeRight(); 63 | } 64 | ``` 65 | 66 | # About me 67 | 微博:[@萧雾宇](http://weibo.com/payge) 68 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "25.0.0" 6 | 7 | defaultConfig { 8 | applicationId "me.payge.swipecardview" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.4.0' 26 | } 27 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\IDE\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/lorentzos/flingswipe/BaseFlingAdapterView.java: -------------------------------------------------------------------------------- 1 | package com.lorentzos.flingswipe; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.AdapterView; 6 | 7 | /** 8 | * Created by dionysis_lorentzos on 6/8/14 9 | * for package com.lorentzos.swipecards 10 | * and project Swipe cards. 11 | * Use with caution dinausaurs might appear! 12 | */ 13 | abstract class BaseFlingAdapterView extends AdapterView { 14 | 15 | private int heightMeasureSpec; 16 | private int widthMeasureSpec; 17 | 18 | 19 | 20 | public BaseFlingAdapterView(Context context) { 21 | super(context); 22 | } 23 | 24 | public BaseFlingAdapterView(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | } 27 | 28 | public BaseFlingAdapterView(Context context, AttributeSet attrs, int defStyle) { 29 | super(context, attrs, defStyle); 30 | } 31 | 32 | @Override 33 | public void setSelection(int i) { 34 | throw new UnsupportedOperationException("Not supported"); 35 | } 36 | 37 | @Override 38 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 39 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 40 | this.widthMeasureSpec = widthMeasureSpec; 41 | this.heightMeasureSpec = heightMeasureSpec; 42 | } 43 | 44 | 45 | public int getWidthMeasureSpec() { 46 | return widthMeasureSpec; 47 | } 48 | 49 | public int getHeightMeasureSpec() { 50 | return heightMeasureSpec; 51 | } 52 | 53 | 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/lorentzos/flingswipe/FlingCardListener.java: -------------------------------------------------------------------------------- 1 | package com.lorentzos.flingswipe; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.view.MotionEvent; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.animation.LinearInterpolator; 9 | import android.view.animation.OvershootInterpolator; 10 | 11 | /** 12 | * Created by dionysis_lorentzos on 5/8/14 13 | * for package com.lorentzos.swipecards 14 | * and project Swipe cards. 15 | * Use with caution dinausaurs might appear! 16 | */ 17 | public class FlingCardListener implements View.OnTouchListener { 18 | 19 | private final float objectX; 20 | private final float objectY; 21 | private final int objectH; 22 | private final int objectW; 23 | private final int parentWidth; 24 | private final FlingListener mFlingListener; 25 | private final Object dataObject; 26 | private final float halfWidth; 27 | private float BASE_ROTATION_DEGREES; 28 | 29 | private float aPosX; 30 | private float aPosY; 31 | private float aDownTouchX; 32 | private float aDownTouchY; 33 | private static final int INVALID_POINTER_ID = -1; 34 | 35 | // The active pointer is the one currently moving our object. 36 | private int mActivePointerId = INVALID_POINTER_ID; 37 | private View frame = null; 38 | 39 | private final int TOUCH_ABOVE = 0; 40 | private final int TOUCH_BELOW = 1; 41 | private int touchPosition; 42 | // private final Object obj = new Object(); 43 | private boolean isAnimationRunning = false; 44 | private float MAX_COS = (float) Math.cos(Math.toRadians(45)); 45 | // 支持左右滑 46 | private boolean isNeedSwipe = true; 47 | 48 | private float aTouchUpX; 49 | 50 | private int animDuration = 300; 51 | private float scale; 52 | 53 | /** 54 | * every time we touch down,we should stop the {@link #animRun} 55 | */ 56 | private boolean resetAnimCanceled = false; 57 | 58 | public FlingCardListener(View frame, Object itemAtPosition, float rotation_degrees, FlingListener flingListener) { 59 | super(); 60 | this.frame = frame; 61 | this.objectX = frame.getX(); 62 | this.objectY = frame.getY(); 63 | this.objectW = frame.getWidth(); 64 | this.objectH = frame.getHeight(); 65 | this.halfWidth = objectW/2f; 66 | this.dataObject = itemAtPosition; 67 | this.parentWidth = ((ViewGroup) frame.getParent()).getWidth(); 68 | this.BASE_ROTATION_DEGREES = rotation_degrees; 69 | this.mFlingListener = flingListener; 70 | } 71 | 72 | public void setIsNeedSwipe(boolean isNeedSwipe) { 73 | this.isNeedSwipe = isNeedSwipe; 74 | } 75 | 76 | @Override 77 | public boolean onTouch(View view, MotionEvent event) { 78 | 79 | try { 80 | switch (event.getAction() & MotionEvent.ACTION_MASK) { 81 | case MotionEvent.ACTION_DOWN: 82 | 83 | // remove the listener because 'onAnimationEnd' will still be called if we cancel the animation. 84 | this.frame.animate().setListener(null); 85 | this.frame.animate().cancel(); 86 | 87 | resetAnimCanceled = true; 88 | 89 | // Save the ID of this pointer 90 | mActivePointerId = event.getPointerId(0); 91 | final float x = event.getX(mActivePointerId); 92 | final float y = event.getY(mActivePointerId); 93 | 94 | // Remember where we started 95 | aDownTouchX = x; 96 | aDownTouchY = y; 97 | // to prevent an initial jump of the magnifier, aposX and aPosY must 98 | // have the values from the magnifier frame 99 | aPosX = frame.getX(); 100 | aPosY = frame.getY(); 101 | 102 | if (y < objectH/2) { 103 | touchPosition = TOUCH_ABOVE; 104 | } else { 105 | touchPosition = TOUCH_BELOW; 106 | } 107 | break; 108 | 109 | case MotionEvent.ACTION_POINTER_DOWN: 110 | break; 111 | 112 | case MotionEvent.ACTION_POINTER_UP: 113 | // Extract the index of the pointer that left the touch sensor 114 | final int pointerIndex = (event.getAction() & 115 | MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; 116 | final int pointerId = event.getPointerId(pointerIndex); 117 | if (pointerId == mActivePointerId) { 118 | // This was our active pointer going up. Choose a new 119 | // active pointer and adjust accordingly. 120 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 121 | mActivePointerId = event.getPointerId(newPointerIndex); 122 | } 123 | break; 124 | case MotionEvent.ACTION_MOVE: 125 | 126 | // Find the index of the active pointer and fetch its position 127 | final int pointerIndexMove = event.findPointerIndex(mActivePointerId); 128 | final float xMove = event.getX(pointerIndexMove); 129 | final float yMove = event.getY(pointerIndexMove); 130 | 131 | // from http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html 132 | // Calculate the distance moved 133 | final float dx = xMove - aDownTouchX; 134 | final float dy = yMove - aDownTouchY; 135 | 136 | // Move the frame 137 | aPosX += dx; 138 | aPosY += dy; 139 | 140 | // calculate the rotation degrees 141 | float distObjectX = aPosX - objectX; 142 | float rotation = BASE_ROTATION_DEGREES * 2f * distObjectX / parentWidth; 143 | if (touchPosition == TOUCH_BELOW) { 144 | rotation = -rotation; 145 | } 146 | 147 | // in this area would be code for doing something with the view as the frame moves. 148 | if (isNeedSwipe) { 149 | frame.setX(aPosX); 150 | frame.setY(aPosY); 151 | frame.setRotation(rotation); 152 | mFlingListener.onScroll(getScrollProgress(), getScrollXProgressPercent()); 153 | } 154 | break; 155 | 156 | case MotionEvent.ACTION_UP: 157 | case MotionEvent.ACTION_CANCEL: 158 | //mActivePointerId = INVALID_POINTER_ID; 159 | int pointerCount = event.getPointerCount(); 160 | int activePointerId = Math.min(mActivePointerId, pointerCount - 1); 161 | aTouchUpX = event.getX(activePointerId); 162 | mActivePointerId = INVALID_POINTER_ID; 163 | resetCardViewOnStack(event); 164 | break; 165 | 166 | } 167 | } catch (Exception e) { 168 | e.printStackTrace(); 169 | } 170 | 171 | return true; 172 | } 173 | 174 | 175 | private float getScrollProgress() { 176 | float dx = aPosX - objectX; 177 | float dy = aPosY - objectY; 178 | float dis = Math.abs(dx) + Math.abs(dy); 179 | return Math.min(dis, 400f) / 400f; 180 | } 181 | 182 | private float getScrollXProgressPercent() { 183 | if (movedBeyondLeftBorder()) { 184 | return -1f; 185 | } else if (movedBeyondRightBorder()) { 186 | return 1f; 187 | } else { 188 | float zeroToOneValue = (aPosX + halfWidth - leftBorder()) / (rightBorder() - leftBorder()); 189 | return zeroToOneValue * 2f - 1f; 190 | } 191 | } 192 | 193 | private boolean resetCardViewOnStack(MotionEvent event) { 194 | if (isNeedSwipe) { 195 | final int duration = 200; 196 | if(movedBeyondLeftBorder()){ 197 | // Left Swipe 198 | onSelected(true, getExitPoint(-objectW), duration); 199 | mFlingListener.onScroll(1f, -1.0f); 200 | }else if(movedBeyondRightBorder()) { 201 | // Right Swipe 202 | onSelected(false, getExitPoint(parentWidth), duration); 203 | mFlingListener.onScroll(1f, 1.0f); 204 | }else{ 205 | float absMoveXDistance = Math.abs(aPosX-objectX); 206 | float absMoveYDistance = Math.abs(aPosY-objectY); 207 | if(absMoveXDistance < 4 && absMoveYDistance < 4){ 208 | mFlingListener.onClick(event, frame, dataObject); 209 | }else{ 210 | frame.animate() 211 | .setDuration(animDuration) 212 | .setInterpolator(new OvershootInterpolator(1.5f)) 213 | .x(objectX) 214 | .y(objectY) 215 | .rotation(0) 216 | .start(); 217 | scale = getScrollProgress(); 218 | this.frame.postDelayed(animRun, 0); 219 | resetAnimCanceled = false; 220 | } 221 | aPosX = 0; 222 | aPosY = 0; 223 | aDownTouchX = 0; 224 | aDownTouchY = 0; 225 | } 226 | } else { 227 | float distanceX = Math.abs(aTouchUpX - aDownTouchX); 228 | if(distanceX < 4) 229 | mFlingListener.onClick(event, frame, dataObject); 230 | } 231 | return false; 232 | } 233 | 234 | private Runnable animRun = new Runnable() { 235 | @Override 236 | public void run() { 237 | mFlingListener.onScroll(scale, 0); 238 | if (scale > 0 && !resetAnimCanceled) { 239 | scale = scale - 0.1f; 240 | if (scale < 0) 241 | scale = 0; 242 | frame.postDelayed(this, animDuration / 20); 243 | } 244 | } 245 | }; 246 | 247 | private boolean movedBeyondLeftBorder() { 248 | return aPosX+halfWidth < leftBorder(); 249 | } 250 | 251 | private boolean movedBeyondRightBorder() { 252 | return aPosX+halfWidth > rightBorder(); 253 | } 254 | 255 | 256 | public float leftBorder(){ 257 | return parentWidth/4f; 258 | } 259 | 260 | public float rightBorder(){ 261 | return 3*parentWidth/4f; 262 | } 263 | 264 | 265 | public void onSelected(final boolean isLeft, float exitY, long duration){ 266 | isAnimationRunning = true; 267 | float exitX; 268 | if(isLeft) { 269 | exitX = -objectW-getRotationWidthOffset(); 270 | }else { 271 | exitX = parentWidth+getRotationWidthOffset(); 272 | } 273 | 274 | this.frame.animate() 275 | .setDuration(duration) 276 | .setInterpolator(new LinearInterpolator()) 277 | .translationX(exitX) 278 | .translationY(exitY) 279 | //.rotation(isLeft ? -BASE_ROTATION_DEGREES:BASE_ROTATION_DEGREES) 280 | .setListener(new AnimatorListenerAdapter() { 281 | @Override 282 | public void onAnimationEnd(Animator animation) { 283 | if (isLeft) { 284 | mFlingListener.onCardExited(); 285 | mFlingListener.leftExit(dataObject); 286 | } else { 287 | mFlingListener.onCardExited(); 288 | mFlingListener.rightExit(dataObject); 289 | } 290 | isAnimationRunning = false; 291 | } 292 | }).start(); 293 | } 294 | 295 | /** 296 | * Starts a default left exit animation. 297 | */ 298 | public void selectLeft(){ 299 | if(!isAnimationRunning) 300 | selectLeft(animDuration); 301 | } 302 | /** 303 | * Starts a default left exit animation. 304 | */ 305 | public void selectLeft(long duration){ 306 | if(!isAnimationRunning) 307 | onSelected(true, objectY, duration); 308 | } 309 | 310 | /** 311 | * Starts a default right exit animation. 312 | */ 313 | public void selectRight(){ 314 | if(!isAnimationRunning) 315 | selectRight(animDuration); 316 | } 317 | /** 318 | * Starts a default right exit animation. 319 | */ 320 | public void selectRight(long duration){ 321 | if(!isAnimationRunning) 322 | onSelected(false, objectY, duration); 323 | } 324 | 325 | private float getExitPoint(int exitXPoint) { 326 | float[] x = new float[2]; 327 | x[0] = objectX; 328 | x[1] = aPosX; 329 | 330 | float[] y = new float[2]; 331 | y[0] = objectY; 332 | y[1] = aPosY; 333 | 334 | LinearRegression regression = new LinearRegression(x,y); 335 | 336 | //Your typical y = ax+b linear regression 337 | return (float) regression.slope() * exitXPoint + (float) regression.intercept(); 338 | } 339 | 340 | private float getExitRotation(boolean isLeft){ 341 | float rotation = BASE_ROTATION_DEGREES * 2f * (parentWidth - objectX)/parentWidth; 342 | if (touchPosition == TOUCH_BELOW) { 343 | rotation = -rotation; 344 | } 345 | if(isLeft){ 346 | rotation = -rotation; 347 | } 348 | return rotation; 349 | } 350 | 351 | /** 352 | * When the object rotates it's width becomes bigger. 353 | * The maximum width is at 45 degrees. 354 | * 355 | * The below method calculates the width offset of the rotation. 356 | * 357 | */ 358 | private float getRotationWidthOffset() { 359 | return objectW/MAX_COS - objectW; 360 | } 361 | 362 | 363 | public void setRotationDegrees(float degrees) { 364 | this.BASE_ROTATION_DEGREES = degrees; 365 | } 366 | 367 | 368 | protected interface FlingListener { 369 | void onCardExited(); 370 | void leftExit(Object dataObject); 371 | void rightExit(Object dataObject); 372 | void onClick(MotionEvent event, View v, Object dataObject); 373 | void onScroll(float progress, float scrollXProgress); 374 | } 375 | 376 | 377 | } 378 | 379 | -------------------------------------------------------------------------------- /app/src/main/java/com/lorentzos/flingswipe/LinearRegression.java: -------------------------------------------------------------------------------- 1 | package com.lorentzos.flingswipe; 2 | 3 | 4 | /************************************************************************* 5 | * Compilation: javac LinearRegression.java 6 | * Execution: java LinearRegression 7 | * 8 | * Compute least squares solution to y = beta * x + alpha. 9 | * Simple linear regression. 10 | * 11 | *************************************************************************/ 12 | 13 | 14 | /** 15 | * The LinearRegression class performs a simple linear regression 16 | * on an set of N data points (yi, xi). 17 | * That is, it fits a straight line y = α + β x, 18 | * (where y is the response variable, x is the predictor variable, 19 | * α is the y-intercept, and β is the slope) 20 | * that minimizes the sum of squared residuals of the linear regression model. 21 | * It also computes associated statistics, including the coefficient of 22 | * determination R2 and the standard deviation of the 23 | * estimates for the slope and y-intercept. 24 | * 25 | * @author Robert Sedgewick 26 | * @author Kevin Wayne 27 | */ 28 | public class LinearRegression { 29 | private final int N; 30 | private final double alpha, beta; 31 | private final double R2; 32 | private final double svar, svar0, svar1; 33 | 34 | /** 35 | * Performs a linear regression on the data points (y[i], x[i]). 36 | * @param x the values of the predictor variable 37 | * @param y the corresponding values of the response variable 38 | * @throws IllegalArgumentException if the lengths of the two arrays are not equal 39 | */ 40 | public LinearRegression(float[] x, float[] y) { 41 | if (x.length != y.length) { 42 | throw new IllegalArgumentException("array lengths are not equal"); 43 | } 44 | N = x.length; 45 | 46 | // first pass 47 | double sumx = 0.0, sumy = 0.0, sumx2 = 0.0; 48 | for (int i = 0; i < N; i++) sumx += x[i]; 49 | for (int i = 0; i < N; i++) sumx2 += x[i]*x[i]; 50 | for (int i = 0; i < N; i++) sumy += y[i]; 51 | double xbar = sumx / N; 52 | double ybar = sumy / N; 53 | 54 | // second pass: compute summary statistics 55 | double xxbar = 0.0, yybar = 0.0, xybar = 0.0; 56 | for (int i = 0; i < N; i++) { 57 | xxbar += (x[i] - xbar) * (x[i] - xbar); 58 | yybar += (y[i] - ybar) * (y[i] - ybar); 59 | xybar += (x[i] - xbar) * (y[i] - ybar); 60 | } 61 | beta = xybar / xxbar; 62 | alpha = ybar - beta * xbar; 63 | 64 | // more statistical analysis 65 | double rss = 0.0; // residual sum of squares 66 | double ssr = 0.0; // regression sum of squares 67 | for (int i = 0; i < N; i++) { 68 | double fit = beta*x[i] + alpha; 69 | rss += (fit - y[i]) * (fit - y[i]); 70 | ssr += (fit - ybar) * (fit - ybar); 71 | } 72 | 73 | int degreesOfFreedom = N-2; 74 | R2 = ssr / yybar; 75 | svar = rss / degreesOfFreedom; 76 | svar1 = svar / xxbar; 77 | svar0 = svar/N + xbar*xbar*svar1; 78 | } 79 | 80 | /** 81 | * Returns the y-intercept α of the best of the best-fit line y = α + β x. 82 | * @return the y-intercept α of the best-fit line y = α + β x 83 | */ 84 | public double intercept() { 85 | return alpha; 86 | } 87 | 88 | /** 89 | * Returns the slope β of the best of the best-fit line y = α + β x. 90 | * @return the slope β of the best-fit line y = α + β x 91 | */ 92 | public double slope() { 93 | return beta; 94 | } 95 | 96 | /** 97 | * Returns the coefficient of determination R2. 98 | * @return the coefficient of determination R2, which is a real number between 0 and 1 99 | */ 100 | public double R2() { 101 | return R2; 102 | } 103 | 104 | /** 105 | * Returns the standard error of the estimate for the intercept. 106 | * @return the standard error of the estimate for the intercept 107 | */ 108 | public double interceptStdErr() { 109 | return Math.sqrt(svar0); 110 | } 111 | 112 | /** 113 | * Returns the standard error of the estimate for the slope. 114 | * @return the standard error of the estimate for the slope 115 | */ 116 | public double slopeStdErr() { 117 | return Math.sqrt(svar1); 118 | } 119 | 120 | /** 121 | * Returns the expected response y given the value of the predictor 122 | * variable x. 123 | * @param x the value of the predictor variable 124 | * @return the expected response y given the value of the predictor 125 | * variable x 126 | */ 127 | public double predict(double x) { 128 | return beta*x + alpha; 129 | } 130 | 131 | /** 132 | * Returns a string representation of the simple linear regression model. 133 | * @return a string representation of the simple linear regression model, 134 | * including the best-fit line and the coefficient of determination R2 135 | */ 136 | public String toString() { 137 | String s = ""; 138 | s += String.format("%.2f N + %.2f", slope(), intercept()); 139 | return s + " (R^2 = " + String.format("%.3f", R2()) + ")"; 140 | } 141 | 142 | 143 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lorentzos/flingswipe/SwipeFlingAdapterView.java: -------------------------------------------------------------------------------- 1 | package com.lorentzos.flingswipe; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.database.DataSetObserver; 7 | import android.os.Build; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.view.Gravity; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.widget.Adapter; 14 | import android.widget.FrameLayout; 15 | 16 | import java.util.ArrayList; 17 | 18 | import me.payge.swipecardview.R; 19 | 20 | 21 | /** 22 | * Created by dionysis_lorentzos on 5/8/14 23 | * for package com.lorentzos.swipecards 24 | * and project Swipe cards. 25 | * Use with caution dinosaurs might appear! 26 | */ 27 | 28 | public class SwipeFlingAdapterView extends BaseFlingAdapterView { 29 | 30 | private ArrayList cacheItems = new ArrayList<>(); 31 | 32 | //缩放层叠效果 33 | private int yOffsetStep; // view叠加垂直偏移量的步长 34 | private static final float SCALE_STEP = 0.08f; // view叠加缩放的步长 35 | //缩放层叠效果 36 | 37 | private int MAX_VISIBLE = 4; // 值建议最小为4 38 | private int MIN_ADAPTER_STACK = 6; 39 | private float ROTATION_DEGREES = 2f; 40 | private int LAST_OBJECT_IN_STACK = 0; 41 | 42 | private Adapter mAdapter; 43 | private onFlingListener mFlingListener; 44 | private AdapterDataSetObserver mDataSetObserver; 45 | private boolean mInLayout = false; 46 | private View mActiveCard = null; 47 | private OnItemClickListener mOnItemClickListener; 48 | private FlingCardListener flingCardListener; 49 | 50 | // 支持左右滑 51 | public boolean isNeedSwipe = true; 52 | 53 | private int initTop; 54 | private int initLeft; 55 | 56 | public SwipeFlingAdapterView(Context context) { 57 | this(context, null); 58 | } 59 | 60 | public SwipeFlingAdapterView(Context context, AttributeSet attrs) { 61 | this(context, attrs, 0); 62 | } 63 | 64 | public SwipeFlingAdapterView(Context context, AttributeSet attrs, int defStyle) { 65 | super(context, attrs, defStyle); 66 | 67 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeFlingAdapterView, defStyle, 0); 68 | MAX_VISIBLE = a.getInt(R.styleable.SwipeFlingAdapterView_max_visible, MAX_VISIBLE); 69 | MIN_ADAPTER_STACK = a.getInt(R.styleable.SwipeFlingAdapterView_min_adapter_stack, MIN_ADAPTER_STACK); 70 | ROTATION_DEGREES = a.getFloat(R.styleable.SwipeFlingAdapterView_rotation_degrees, ROTATION_DEGREES); 71 | yOffsetStep = a.getDimensionPixelOffset(R.styleable.SwipeFlingAdapterView_y_offset_step, 0); 72 | a.recycle(); 73 | 74 | } 75 | 76 | public void setIsNeedSwipe(boolean isNeedSwipe) { 77 | this.isNeedSwipe = isNeedSwipe; 78 | } 79 | 80 | /** 81 | * A shortcut method to set both the listeners and the adapter. 82 | * 83 | * @param context The activity context which extends onFlingListener, OnItemClickListener or both 84 | * @param mAdapter The adapter you have to set. 85 | */ 86 | public void init(final Context context, Adapter mAdapter) { 87 | if(context instanceof onFlingListener) { 88 | mFlingListener = (onFlingListener) context; 89 | }else{ 90 | throw new RuntimeException("Activity does not implement SwipeFlingAdapterView.onFlingListener"); 91 | } 92 | if(context instanceof OnItemClickListener){ 93 | mOnItemClickListener = (OnItemClickListener) context; 94 | } 95 | setAdapter(mAdapter); 96 | } 97 | 98 | @Override 99 | public View getSelectedView() { 100 | return mActiveCard; 101 | } 102 | 103 | 104 | @Override 105 | public void requestLayout() { 106 | if (!mInLayout) { 107 | super.requestLayout(); 108 | } 109 | } 110 | 111 | @Override 112 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 113 | super.onLayout(changed, left, top, right, bottom); 114 | // if we don't have an adapter, we don't need to do anything 115 | if (mAdapter == null) { 116 | return; 117 | } 118 | 119 | mInLayout = true; 120 | final int adapterCount = mAdapter.getCount(); 121 | if (adapterCount == 0) { 122 | // removeAllViewsInLayout(); 123 | removeAndAddToCache(0); 124 | } else { 125 | View topCard = getChildAt(LAST_OBJECT_IN_STACK); 126 | if(mActiveCard != null && topCard != null && topCard == mActiveCard) { 127 | // removeViewsInLayout(0, LAST_OBJECT_IN_STACK); 128 | removeAndAddToCache(1); 129 | layoutChildren(1, adapterCount); 130 | }else{ 131 | // Reset the UI and set top view listener 132 | // removeAllViewsInLayout(); 133 | removeAndAddToCache(0); 134 | layoutChildren(0, adapterCount); 135 | setTopView(); 136 | } 137 | } 138 | mInLayout = false; 139 | 140 | if (initTop == 0 && initLeft == 0 && mActiveCard != null) { 141 | initTop = mActiveCard.getTop(); 142 | initLeft = mActiveCard.getLeft(); 143 | } 144 | 145 | if(adapterCount < MIN_ADAPTER_STACK) { 146 | if(mFlingListener != null){ 147 | mFlingListener.onAdapterAboutToEmpty(adapterCount); 148 | } 149 | } 150 | } 151 | 152 | private void removeAndAddToCache(int remain) { 153 | View view; 154 | for (int i = 0; i < getChildCount() - remain; ) { 155 | view = getChildAt(i); 156 | removeViewInLayout(view); 157 | cacheItems.add(view); 158 | } 159 | } 160 | 161 | private void layoutChildren(int startingIndex, int adapterCount){ 162 | while (startingIndex < Math.min(adapterCount, MAX_VISIBLE) ) { 163 | View item = null; 164 | if (cacheItems.size() > 0) { 165 | item = cacheItems.get(0); 166 | cacheItems.remove(item); 167 | } 168 | View newUnderChild = mAdapter.getView(startingIndex, item, this); 169 | if (newUnderChild.getVisibility() != GONE) { 170 | makeAndAddView(newUnderChild, startingIndex); 171 | LAST_OBJECT_IN_STACK = startingIndex; 172 | } 173 | startingIndex++; 174 | } 175 | } 176 | 177 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 178 | private void makeAndAddView(View child, int index) { 179 | 180 | FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams(); 181 | addViewInLayout(child, 0, lp, true); 182 | 183 | final boolean needToMeasure = child.isLayoutRequested(); 184 | if (needToMeasure) { 185 | int childWidthSpec = getChildMeasureSpec(getWidthMeasureSpec(), 186 | getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, 187 | lp.width); 188 | int childHeightSpec = getChildMeasureSpec(getHeightMeasureSpec(), 189 | getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, 190 | lp.height); 191 | child.measure(childWidthSpec, childHeightSpec); 192 | } else { 193 | cleanupLayoutState(child); 194 | } 195 | 196 | int w = child.getMeasuredWidth(); 197 | int h = child.getMeasuredHeight(); 198 | 199 | int gravity = lp.gravity; 200 | if (gravity == -1) { 201 | gravity = Gravity.TOP | Gravity.START; 202 | } 203 | 204 | int layoutDirection = 0; 205 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) 206 | layoutDirection = getLayoutDirection(); 207 | final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); 208 | final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; 209 | 210 | int childLeft; 211 | int childTop; 212 | switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 213 | case Gravity.CENTER_HORIZONTAL: 214 | childLeft = (getWidth() + getPaddingLeft() - getPaddingRight() - w) / 2 + 215 | lp.leftMargin - lp.rightMargin; 216 | break; 217 | case Gravity.END: 218 | childLeft = getWidth() + getPaddingRight() - w - lp.rightMargin; 219 | break; 220 | case Gravity.START: 221 | default: 222 | childLeft = getPaddingLeft() + lp.leftMargin; 223 | break; 224 | } 225 | switch (verticalGravity) { 226 | case Gravity.CENTER_VERTICAL: 227 | childTop = (getHeight() + getPaddingTop() - getPaddingBottom() - h) / 2 + 228 | lp.topMargin - lp.bottomMargin; 229 | break; 230 | case Gravity.BOTTOM: 231 | childTop = getHeight() - getPaddingBottom() - h - lp.bottomMargin; 232 | break; 233 | case Gravity.TOP: 234 | default: 235 | childTop = getPaddingTop() + lp.topMargin; 236 | break; 237 | } 238 | child.layout(childLeft, childTop, childLeft + w, childTop + h); 239 | // 缩放层叠效果 240 | adjustChildView(child, index); 241 | } 242 | 243 | private void adjustChildView(View child, int index) { 244 | if (index > -1 && index < MAX_VISIBLE) { 245 | int multiple; 246 | if (index > 2) multiple = 2; 247 | else multiple = index; 248 | child.offsetTopAndBottom(yOffsetStep * multiple); 249 | child.setScaleX(1 - SCALE_STEP * multiple); 250 | child.setScaleY(1 - SCALE_STEP * multiple); 251 | } 252 | } 253 | 254 | private void adjustChildrenOfUnderTopView(float scrollRate) { 255 | int count = getChildCount(); 256 | if (count > 1) { 257 | int i; 258 | int multiple; 259 | if (count == 2) { 260 | i = LAST_OBJECT_IN_STACK - 1; 261 | multiple = 1; 262 | } else { 263 | i = LAST_OBJECT_IN_STACK - 2; 264 | multiple = 2; 265 | } 266 | float rate = Math.abs(scrollRate); 267 | for (; i < LAST_OBJECT_IN_STACK; i++, multiple--) { 268 | View underTopView = getChildAt(i); 269 | int offset = (int) (yOffsetStep * (multiple - rate)); 270 | underTopView.offsetTopAndBottom(offset - underTopView.getTop() + initTop); 271 | underTopView.setScaleX(1 - SCALE_STEP * multiple + SCALE_STEP * rate); 272 | underTopView.setScaleY(1 - SCALE_STEP * multiple + SCALE_STEP * rate); 273 | } 274 | } 275 | } 276 | 277 | /** 278 | * Set the top view and add the fling listener 279 | */ 280 | private void setTopView() { 281 | if(getChildCount()>0){ 282 | 283 | mActiveCard = getChildAt(LAST_OBJECT_IN_STACK); 284 | if(mActiveCard != null && mFlingListener != null) { 285 | 286 | flingCardListener = new FlingCardListener(mActiveCard, mAdapter.getItem(0), 287 | ROTATION_DEGREES, new FlingCardListener.FlingListener() { 288 | 289 | @Override 290 | public void onCardExited() { 291 | removeViewInLayout(mActiveCard); 292 | mActiveCard = null; 293 | mFlingListener.removeFirstObjectInAdapter(); 294 | } 295 | 296 | @Override 297 | public void leftExit(Object dataObject) { 298 | mFlingListener.onLeftCardExit(dataObject); 299 | } 300 | 301 | @Override 302 | public void rightExit(Object dataObject) { 303 | mFlingListener.onRightCardExit(dataObject); 304 | } 305 | 306 | @Override 307 | public void onClick(MotionEvent event, View v, Object dataObject) { 308 | if(mOnItemClickListener != null) 309 | mOnItemClickListener.onItemClicked(event, v, dataObject); 310 | } 311 | 312 | @Override 313 | public void onScroll(float progress, float scrollXProgress) { 314 | // Log.e("Log", "onScroll " + progress); 315 | adjustChildrenOfUnderTopView(progress); 316 | mFlingListener.onScroll(progress, scrollXProgress); 317 | } 318 | }); 319 | // 设置是否支持左右滑 320 | flingCardListener.setIsNeedSwipe(isNeedSwipe); 321 | 322 | mActiveCard.setOnTouchListener(flingCardListener); 323 | } 324 | } 325 | } 326 | 327 | public FlingCardListener getTopCardListener() throws NullPointerException { 328 | if(flingCardListener==null){ 329 | throw new NullPointerException("flingCardListener is null"); 330 | } 331 | return flingCardListener; 332 | } 333 | 334 | public void setMaxVisible(int MAX_VISIBLE){ 335 | this.MAX_VISIBLE = MAX_VISIBLE; 336 | } 337 | 338 | public void setMinStackInAdapter(int MIN_ADAPTER_STACK) { 339 | this.MIN_ADAPTER_STACK = MIN_ADAPTER_STACK; 340 | } 341 | 342 | /** 343 | * click to swipe left 344 | */ 345 | public void swipeLeft() { 346 | getTopCardListener().selectLeft(); 347 | } 348 | 349 | public void swipeLeft(int duration) { 350 | getTopCardListener().selectLeft(duration); 351 | } 352 | 353 | /** 354 | * click to swipe right 355 | */ 356 | public void swipeRight() { 357 | getTopCardListener().selectRight(); 358 | } 359 | 360 | public void swipeRight(int duration) { 361 | getTopCardListener().selectRight(duration); 362 | } 363 | 364 | @Override 365 | public Adapter getAdapter() { 366 | return mAdapter; 367 | } 368 | 369 | 370 | @Override 371 | public void setAdapter(Adapter adapter) { 372 | if (mAdapter != null && mDataSetObserver != null) { 373 | mAdapter.unregisterDataSetObserver(mDataSetObserver); 374 | mDataSetObserver = null; 375 | } 376 | 377 | mAdapter = adapter; 378 | 379 | if (mAdapter != null && mDataSetObserver == null) { 380 | mDataSetObserver = new AdapterDataSetObserver(); 381 | mAdapter.registerDataSetObserver(mDataSetObserver); 382 | } 383 | } 384 | 385 | public void setFlingListener(onFlingListener onFlingListener) { 386 | this.mFlingListener = onFlingListener; 387 | } 388 | 389 | public void setOnItemClickListener(OnItemClickListener onItemClickListener){ 390 | this.mOnItemClickListener = onItemClickListener; 391 | } 392 | 393 | 394 | @Override 395 | public LayoutParams generateLayoutParams(AttributeSet attrs) { 396 | return new FrameLayout.LayoutParams(getContext(), attrs); 397 | } 398 | 399 | 400 | private class AdapterDataSetObserver extends DataSetObserver { 401 | @Override 402 | public void onChanged() { 403 | requestLayout(); 404 | } 405 | 406 | @Override 407 | public void onInvalidated() { 408 | requestLayout(); 409 | } 410 | 411 | } 412 | 413 | 414 | public interface OnItemClickListener { 415 | void onItemClicked(MotionEvent event, View v, Object dataObject); 416 | } 417 | 418 | public interface onFlingListener { 419 | void removeFirstObjectInAdapter(); 420 | void onLeftCardExit(Object dataObject); 421 | void onRightCardExit(Object dataObject); 422 | void onAdapterAboutToEmpty(int itemsInAdapter); 423 | void onScroll(float progress, float scrollXProgress); 424 | } 425 | 426 | 427 | } 428 | -------------------------------------------------------------------------------- /app/src/main/java/me/payge/swipecardview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.payge.swipecardview; 2 | 3 | import android.os.AsyncTask; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.util.DisplayMetrics; 7 | import android.view.LayoutInflater; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.BaseAdapter; 12 | import android.widget.CheckedTextView; 13 | import android.widget.ImageView; 14 | import android.widget.TextView; 15 | 16 | import com.lorentzos.flingswipe.SwipeFlingAdapterView; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.List; 21 | import java.util.Random; 22 | 23 | public class MainActivity extends AppCompatActivity implements SwipeFlingAdapterView.onFlingListener, 24 | SwipeFlingAdapterView.OnItemClickListener, View.OnClickListener { 25 | 26 | int [] headerIcons = { 27 | R.drawable.i1, 28 | R.drawable.i2, 29 | R.drawable.i3, 30 | R.drawable.i4, 31 | R.drawable.i5, 32 | R.drawable.i6 33 | }; 34 | 35 | String [] names = {"张三","李四","王五","小明","小红","小花"}; 36 | 37 | String [] citys = {"北京", "上海", "广州", "深圳"}; 38 | 39 | String [] edus = {"大专", "本科", "硕士", "博士"}; 40 | 41 | String [] years = {"1年", "2年", "3年", "4年", "5年"}; 42 | 43 | Random ran = new Random(); 44 | 45 | private int cardWidth; 46 | private int cardHeight; 47 | 48 | private SwipeFlingAdapterView swipeView; 49 | private InnerAdapter adapter; 50 | 51 | 52 | @Override 53 | protected void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | setContentView(R.layout.activity_main); 56 | 57 | initView(); 58 | loadData(); 59 | } 60 | 61 | private void initView() { 62 | DisplayMetrics dm = getResources().getDisplayMetrics(); 63 | float density = dm.density; 64 | cardWidth = (int) (dm.widthPixels - (2 * 18 * density)); 65 | cardHeight = (int) (dm.heightPixels - (338 * density)); 66 | 67 | 68 | swipeView = (SwipeFlingAdapterView) findViewById(R.id.swipe_view); 69 | if (swipeView != null) { 70 | swipeView.setIsNeedSwipe(true); 71 | swipeView.setFlingListener(this); 72 | swipeView.setOnItemClickListener(this); 73 | 74 | adapter = new InnerAdapter(); 75 | swipeView.setAdapter(adapter); 76 | } 77 | 78 | View v = findViewById(R.id.swipeLeft); 79 | if (v != null) { 80 | v.setOnClickListener(this); 81 | } 82 | v = findViewById(R.id.swipeRight); 83 | if (v != null) { 84 | v.setOnClickListener(this); 85 | } 86 | 87 | } 88 | 89 | 90 | @Override 91 | public void onItemClicked(MotionEvent event, View v, Object dataObject) { 92 | } 93 | 94 | @Override 95 | public void removeFirstObjectInAdapter() { 96 | adapter.remove(0); 97 | } 98 | 99 | @Override 100 | public void onLeftCardExit(Object dataObject) { 101 | } 102 | 103 | @Override 104 | public void onRightCardExit(Object dataObject) { 105 | } 106 | 107 | @Override 108 | public void onAdapterAboutToEmpty(int itemsInAdapter) { 109 | if (itemsInAdapter == 3) { 110 | loadData(); 111 | } 112 | } 113 | 114 | @Override 115 | public void onScroll(float progress, float scrollXProgress) { 116 | } 117 | 118 | @Override 119 | public void onClick(View v) { 120 | switch (v.getId()) { 121 | case R.id.swipeLeft: 122 | swipeView.swipeLeft(); 123 | //swipeView.swipeLeft(250); 124 | break; 125 | case R.id.swipeRight: 126 | swipeView.swipeRight(); 127 | //swipeView.swipeRight(250); 128 | } 129 | } 130 | 131 | private void loadData() { 132 | new AsyncTask>() { 133 | @Override 134 | protected List doInBackground(Void... params) { 135 | ArrayList list = new ArrayList<>(10); 136 | Talent talent; 137 | for (int i = 0; i < 10; i++) { 138 | talent = new Talent(); 139 | talent.headerIcon = headerIcons[i % headerIcons.length]; 140 | talent.nickname = names[ran.nextInt(names.length-1)]; 141 | talent.cityName = citys[ran.nextInt(citys.length-1)]; 142 | talent.educationName = edus[ran.nextInt(edus.length-1)]; 143 | talent.workYearName = years[ran.nextInt(years.length-1)]; 144 | list.add(talent); 145 | } 146 | return list; 147 | } 148 | 149 | @Override 150 | protected void onPostExecute(List list) { 151 | super.onPostExecute(list); 152 | adapter.addAll(list); 153 | } 154 | }.execute(); 155 | } 156 | 157 | 158 | private class InnerAdapter extends BaseAdapter { 159 | 160 | ArrayList objs; 161 | 162 | public InnerAdapter() { 163 | objs = new ArrayList<>(); 164 | } 165 | 166 | public void addAll(Collection collection) { 167 | if (isEmpty()) { 168 | objs.addAll(collection); 169 | notifyDataSetChanged(); 170 | } else { 171 | objs.addAll(collection); 172 | } 173 | } 174 | 175 | public void clear() { 176 | objs.clear(); 177 | notifyDataSetChanged(); 178 | } 179 | 180 | public boolean isEmpty() { 181 | return objs.isEmpty(); 182 | } 183 | 184 | public void remove(int index) { 185 | if (index > -1 && index < objs.size()) { 186 | objs.remove(index); 187 | notifyDataSetChanged(); 188 | } 189 | } 190 | 191 | 192 | @Override 193 | public int getCount() { 194 | return objs.size(); 195 | } 196 | 197 | @Override 198 | public Talent getItem(int position) { 199 | if(objs==null ||objs.size()==0) return null; 200 | return objs.get(position); 201 | } 202 | 203 | @Override 204 | public long getItemId(int position) { 205 | return position; 206 | } 207 | 208 | // TODO: getView 209 | @Override 210 | public View getView(int position, View convertView, ViewGroup parent) { 211 | ViewHolder holder; 212 | Talent talent = getItem(position); 213 | if (convertView == null) { 214 | convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_new_item, parent, false); 215 | holder = new ViewHolder(); 216 | convertView.setTag(holder); 217 | convertView.getLayoutParams().width = cardWidth; 218 | holder.portraitView = (ImageView) convertView.findViewById(R.id.portrait); 219 | //holder.portraitView.getLayoutParams().width = cardWidth; 220 | holder.portraitView.getLayoutParams().height = cardHeight; 221 | holder.nameView = (TextView) convertView.findViewById(R.id.name); 222 | //parentView.getLayoutParams().width = cardWidth; 223 | //holder.jobView = (TextView) convertView.findViewById(R.id.job); 224 | //holder.companyView = (TextView) convertView.findViewById(R.id.company); 225 | holder.cityView = (TextView) convertView.findViewById(R.id.city); 226 | holder.eduView = (TextView) convertView.findViewById(R.id.education); 227 | holder.workView = (TextView) convertView.findViewById(R.id.work_year); 228 | } else { 229 | holder = (ViewHolder) convertView.getTag(); 230 | } 231 | 232 | 233 | holder.portraitView.setImageResource(talent.headerIcon); 234 | 235 | holder.nameView.setText(String.format("%s", talent.nickname)); 236 | //holder.jobView.setText(talent.jobName); 237 | 238 | final CharSequence no = "暂无"; 239 | 240 | holder.cityView.setHint(no); 241 | holder.cityView.setText(talent.cityName); 242 | holder.cityView.setCompoundDrawablesWithIntrinsicBounds(0,R.drawable.home01_icon_location,0,0); 243 | 244 | holder.eduView.setHint(no); 245 | holder.eduView.setText(talent.educationName); 246 | holder.eduView.setCompoundDrawablesWithIntrinsicBounds(0,R.drawable.home01_icon_edu,0,0); 247 | 248 | holder.workView.setHint(no); 249 | holder.workView.setText(talent.workYearName); 250 | holder.workView.setCompoundDrawablesWithIntrinsicBounds(0,R.drawable.home01_icon_work_year,0,0); 251 | 252 | 253 | return convertView; 254 | } 255 | 256 | } 257 | 258 | private static class ViewHolder { 259 | ImageView portraitView; 260 | TextView nameView; 261 | TextView cityView; 262 | TextView eduView; 263 | TextView workView; 264 | CheckedTextView collectView; 265 | 266 | } 267 | 268 | public static class Talent { 269 | public int headerIcon; 270 | public String nickname; 271 | public String cityName; 272 | public String educationName; 273 | public String workYearName; 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/default_card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/default_card.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/home01_bg_card.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/home01_bg_card.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/home01_btn_collect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/home01_btn_collect.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/home01_icon_edu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/home01_icon_edu.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/home01_icon_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/home01_icon_location.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/home01_icon_work_year.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/home01_icon_work_year.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/i1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/i1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/i2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/i2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/i3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/i3.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/i4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/i4.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/i5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/i5.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/i6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepeijie/SwipeCardView/b8caed6296fd1ff1547cf022e62c45b71c0ee11b/app/src/main/res/drawable-xxhdpi/i6.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 15 | 22 |