├── tinder-swipe.gif ├── ic_launcher-web.png ├── libs └── android-support-v4.jar ├── res ├── drawable-xhdpi │ ├── card.png │ ├── card2.png │ ├── ic_launcher.png │ ├── collect_fore.png │ └── recommend_app_icon.png ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── values-v21 │ └── styles.xml ├── values │ ├── styles.xml │ ├── strings.xml │ ├── dimens.xml │ ├── attrs.xml │ └── colors.xml ├── values-sw600dp │ └── dimens.xml ├── menu │ ├── main.xml │ └── my.xml ├── values-sw720dp-land │ └── dimens.xml ├── values-v11 │ └── styles.xml ├── values-w820dp │ └── dimens.xml ├── values-v14 │ └── styles.xml └── layout │ ├── activity_main.xml │ ├── no_circle_item.xml │ ├── box_card_layout.xml │ ├── item.xml │ └── activity_my.xml ├── README.md ├── src └── com │ └── yiqivr │ └── tinderswipe │ ├── widget │ ├── OnBaseFlingListener.java │ ├── OnLeftRightFlingListener.java │ ├── OnTopBottomFlingListener.java │ ├── OnTopBottomFlingWithProportionListener.java │ ├── HelperFlingWithProportionListener.java │ ├── BaseFlingAdapterView.java │ ├── MultiViewPager.java │ ├── CircleProgress.java │ ├── LinearRegression.java │ ├── SwipeFlingAdapterView.java │ ├── BoxLayout.java │ └── FlingCardListener.java │ └── MyActivity.java ├── .gitignore ├── .classpath ├── project.properties ├── proguard-project.txt ├── .project └── AndroidManifest.xml /tinder-swipe.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/tinder-swipe.gif -------------------------------------------------------------------------------- /ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/ic_launcher-web.png -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/libs/android-support-v4.jar -------------------------------------------------------------------------------- /res/drawable-xhdpi/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/res/drawable-xhdpi/card.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/card2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/res/drawable-xhdpi/card2.png -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/collect_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/res/drawable-xhdpi/collect_fore.png -------------------------------------------------------------------------------- /res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/recommend_app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvning/TinderSwipe/HEAD/res/drawable-xhdpi/recommend_app_icon.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TinderSwipe 2 | =========== 3 | tinder-like effect 4 | 5 | ![image](https://github.com/lvning/TinderSwipe/blob/master/tinder-swipe.gif) 6 | -------------------------------------------------------------------------------- /res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/OnBaseFlingListener.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | public interface OnBaseFlingListener { 4 | void removeFirstObjectInAdapter(); 5 | 6 | void onAdapterAboutToEmpty(int itemsInAdapter); 7 | } 8 | -------------------------------------------------------------------------------- /res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Swipe cards 5 | Hello world! 6 | Settings 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/OnLeftRightFlingListener.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | public interface OnLeftRightFlingListener extends OnBaseFlingListener { 4 | void onLeftCardExit(Object dataObject); 5 | void onRightCardExit(Object dataObject); 6 | } 7 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/OnTopBottomFlingListener.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | 4 | public interface OnTopBottomFlingListener extends OnBaseFlingListener { 5 | void onTopCardExit(Object dataObject); 6 | 7 | void onBottomCardExit(Object dataObject); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 10dp 6 | 7 | -------------------------------------------------------------------------------- /res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 128dp 8 | 9 | 10 | -------------------------------------------------------------------------------- /res/menu/my.xml: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | -------------------------------------------------------------------------------- /res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/OnTopBottomFlingWithProportionListener.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * @author lvning 7 | * @version create time:2014-9-25_上午10:20:00 8 | * @Description TODO 9 | */ 10 | public interface OnTopBottomFlingWithProportionListener extends OnTopBottomFlingListener { 11 | 12 | public void onFlingSuccessPercent(View selfView, int percent, boolean flingTop); 13 | 14 | public void onFlingResetOrigin(View selfView); 15 | } 16 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/HelperFlingWithProportionListener.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | import android.view.View; 4 | 5 | public interface HelperFlingWithProportionListener { 6 | public void onCardExited(); 7 | 8 | public void leftExit(Object dataObject); 9 | 10 | public void rightExit(Object dataObject); 11 | 12 | public void topExit(Object dataObject); 13 | 14 | public void bottomExit(Object dataObject); 15 | 16 | public void flingResetOrigin(View selfView); 17 | 18 | public void flingOccurPercent(View selfView, int percent, boolean swipeTop); 19 | } 20 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | -------------------------------------------------------------------------------- /res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | #00ff00 21 | #ff0000 22 | 23 | -------------------------------------------------------------------------------- /res/layout/no_circle_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | TinderSwipe 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/BaseFlingAdapterView.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.AdapterView; 7 | 8 | public abstract class BaseFlingAdapterView extends AdapterView { 9 | 10 | private int heightMeasureSpec; 11 | private int widthMeasureSpec; 12 | 13 | public BaseFlingAdapterView(Context context) { 14 | super(context); 15 | } 16 | 17 | public BaseFlingAdapterView(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | } 20 | 21 | public BaseFlingAdapterView(Context context, AttributeSet attrs, int defStyle) { 22 | super(context, attrs, defStyle); 23 | } 24 | 25 | @Override 26 | public View getSelectedView() { 27 | throw new UnsupportedOperationException("Not supported"); 28 | } 29 | 30 | @Override 31 | public void setSelection(int i) { 32 | throw new UnsupportedOperationException("Not supported"); 33 | } 34 | 35 | @Override 36 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 37 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 38 | this.widthMeasureSpec = widthMeasureSpec; 39 | this.heightMeasureSpec = heightMeasureSpec; 40 | } 41 | 42 | public int getWidthMeasureSpec() { 43 | return widthMeasureSpec; 44 | } 45 | 46 | public int getHeightMeasureSpec() { 47 | return heightMeasureSpec; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /res/layout/box_card_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 17 | 18 | 30 | 31 | 43 | 44 | -------------------------------------------------------------------------------- /res/layout/item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 31 | 32 | 42 | 43 | -------------------------------------------------------------------------------- /res/layout/activity_my.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 21 | 22 | 28 | 29 | 37 | 38 | 45 | 46 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/MultiViewPager.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Point; 6 | import android.support.v4.view.ViewPager; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import com.yiqivr.tinderswipe.R; 11 | 12 | /** 13 | * @author lvning 14 | * @version create time:2014-10-5_下午12:52:01 15 | * @Description TODO 16 | */ 17 | public class MultiViewPager extends ViewPager { 18 | 19 | /** 20 | * Maximum size. 21 | */ 22 | private int mMaxWidth = -1; 23 | /** 24 | * Maximum size. 25 | */ 26 | private int mMaxHeight = -1; 27 | /** 28 | * Child view inside a page to match the page size against. 29 | */ 30 | private int mMatchWidthChildResId; 31 | 32 | /** 33 | * Internal state to schedule a new measurement pass. 34 | */ 35 | private boolean mNeedsMeasurePage; 36 | 37 | private static void constrainTo(Point size, Point maxSize) { 38 | if (maxSize.x >= 0) { 39 | if (size.x > maxSize.x) { 40 | size.x = maxSize.x; 41 | } 42 | } 43 | if (maxSize.y >= 0) { 44 | if (size.y > maxSize.y) { 45 | size.y = maxSize.y; 46 | } 47 | } 48 | } 49 | 50 | public MultiViewPager(Context context) { 51 | super(context); 52 | } 53 | 54 | public MultiViewPager(Context context, AttributeSet attrs) { 55 | super(context, attrs); 56 | init(context, attrs); 57 | } 58 | 59 | private void init(Context context, AttributeSet attrs) { 60 | setClipChildren(false); 61 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MultiViewPager); 62 | setMaxWidth(ta.getDimensionPixelSize(R.styleable.MultiViewPager_android_maxWidth, -1)); 63 | setMaxHeight(ta.getDimensionPixelSize(R.styleable.MultiViewPager_android_maxHeight, -1)); 64 | setMatchChildWidth(ta.getResourceId(R.styleable.MultiViewPager_matchChildWidth, 0)); 65 | ta.recycle(); 66 | } 67 | 68 | @Override 69 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 70 | Point size = new Point( 71 | MeasureSpec.getSize(widthMeasureSpec), 72 | MeasureSpec.getSize(heightMeasureSpec)); 73 | if (mMaxWidth >= 0 || mMaxHeight >= 0) { 74 | Point maxSize = new Point(mMaxWidth, mMaxHeight); 75 | constrainTo(size, maxSize); 76 | widthMeasureSpec = MeasureSpec.makeMeasureSpec( 77 | size.x, 78 | MeasureSpec.EXACTLY); 79 | heightMeasureSpec = MeasureSpec.makeMeasureSpec( 80 | size.y, 81 | MeasureSpec.EXACTLY); 82 | } 83 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 84 | onMeasurePage(widthMeasureSpec, heightMeasureSpec); 85 | } 86 | 87 | protected void onMeasurePage(int widthMeasureSpec, int heightMeasureSpec) { 88 | // Only measure if a measurement pass was scheduled 89 | if (!mNeedsMeasurePage) { 90 | return; 91 | } 92 | if (mMatchWidthChildResId == 0) { 93 | mNeedsMeasurePage = false; 94 | } else if (getChildCount() > 0) { 95 | View child = getChildAt(0); 96 | child.measure(widthMeasureSpec, heightMeasureSpec); 97 | int pageWidth = child.getMeasuredWidth(); 98 | View match = child.findViewById(mMatchWidthChildResId); 99 | if (match == null) { 100 | throw new NullPointerException( 101 | "MatchWithChildResId did not find that ID in the first fragment of the ViewPager; " 102 | + "is that view defined in the child view's layout? Note that MultiViewPager " 103 | + "only measures the child for index 0."); 104 | } 105 | int childWidth = match.getMeasuredWidth(); 106 | // Check that the measurement was successful 107 | if (childWidth > 0) { 108 | mNeedsMeasurePage = false; 109 | int difference = pageWidth - childWidth; 110 | setPageMargin(-difference); 111 | int offscreen = (int) Math.ceil((float) pageWidth / (float) childWidth) + 1; 112 | setOffscreenPageLimit(offscreen); 113 | requestLayout(); 114 | } 115 | } 116 | } 117 | 118 | @Override 119 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 120 | super.onSizeChanged(w, h, oldw, oldh); 121 | // Schedule a new measurement pass as the dimensions have changed 122 | mNeedsMeasurePage = true; 123 | } 124 | 125 | /** 126 | * Sets the child view inside a page to match the page size against. 127 | * 128 | * @param matchChildWidthResId 129 | */ 130 | public void setMatchChildWidth(int matchChildWidthResId) { 131 | if (mMatchWidthChildResId != matchChildWidthResId) { 132 | mMatchWidthChildResId = matchChildWidthResId; 133 | mNeedsMeasurePage = true; 134 | } 135 | } 136 | 137 | /** 138 | * Sets the maximum size. 139 | * 140 | * @param width 141 | */ 142 | public void setMaxWidth(int width) { 143 | mMaxWidth = width; 144 | } 145 | 146 | /** 147 | * Sets the maximum size. 148 | * 149 | * @param height 150 | */ 151 | public void setMaxHeight(int height) { 152 | mMaxHeight = height; 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/CircleProgress.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.RectF; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | 14 | import com.yiqivr.tinderswipe.R; 15 | 16 | /** 17 | * @author lvning 18 | * @version create time:2014-9-15_下午4:13:50 19 | * @Description 圆形进度 20 | */ 21 | public class CircleProgress extends View { 22 | 23 | private CircleAttribute mCircleAttribute; 24 | private int mMaxProgress = 100; 25 | private int mSubCurProgress; 26 | private int backColor, foreColor; 27 | private Bitmap centerImg; 28 | 29 | public CircleProgress(Context paramContext) { 30 | this(paramContext, null); 31 | } 32 | 33 | public CircleProgress(Context paramContext, AttributeSet paramAttributeSet) { 34 | super(paramContext, paramAttributeSet); 35 | init(paramContext, paramAttributeSet); 36 | defaultParam(paramContext); 37 | } 38 | 39 | private void init(Context paramContext, AttributeSet paramAttributeSet) { 40 | final TypedArray ta = paramContext.obtainStyledAttributes(paramAttributeSet, R.styleable.CircleProgress); 41 | backColor = ta.getColor(R.styleable.CircleProgress_backColor, Color.parseColor("#898989")); 42 | foreColor = ta.getColor(R.styleable.CircleProgress_foreColor, Color.parseColor("#13DEFF")); 43 | int centerImgId = ta.getResourceId(R.styleable.CircleProgress_centerPic, android.R.drawable.star_on); 44 | centerImg = BitmapFactory.decodeResource(getResources(), centerImgId); 45 | ta.recycle(); 46 | } 47 | 48 | private void defaultParam(Context paramContext) { 49 | this.mCircleAttribute = new CircleAttribute(); 50 | this.mMaxProgress = 100; 51 | this.mSubCurProgress = 0; 52 | } 53 | 54 | @Override 55 | public void onDraw(Canvas canvas) { 56 | super.onDraw(canvas); 57 | float f1 = 360.0F * this.mSubCurProgress / this.mMaxProgress; 58 | canvas.drawArc(this.mCircleAttribute.inRoundOval, 0.0F, 360.0F, this.mCircleAttribute.mBRoundPaintsFill, 59 | this.mCircleAttribute.circlePaint); 60 | canvas.drawArc(this.mCircleAttribute.mRoundOval, 0.0F, 360.0F, this.mCircleAttribute.mBRoundPaintsFill, 61 | this.mCircleAttribute.mBottomPaint); 62 | canvas.drawArc(this.mCircleAttribute.mRoundOval, this.mCircleAttribute.mDrawPos, f1, 63 | this.mCircleAttribute.mBRoundPaintsFill, this.mCircleAttribute.mSubPaint); 64 | canvas.drawBitmap(centerImg, this.mCircleAttribute.inRoundOval.centerX() - centerImg.getWidth() / 2, 65 | this.mCircleAttribute.inRoundOval.top + centerImg.getHeight() / 2, null); 66 | } 67 | 68 | @Override 69 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 70 | int i = View.MeasureSpec.getSize(widthMeasureSpec); 71 | View.MeasureSpec.getSize(heightMeasureSpec); 72 | setMeasuredDimension(resolveSize(i, widthMeasureSpec), resolveSize(i, heightMeasureSpec)); 73 | } 74 | 75 | @Override 76 | protected void onSizeChanged(int paramInt1, int paramInt2, int paramInt3, int paramInt4) { 77 | super.onSizeChanged(paramInt1, paramInt2, paramInt3, paramInt4); 78 | this.mCircleAttribute.autoFix(paramInt1, paramInt2); 79 | } 80 | 81 | /** 82 | * 设置进度 83 | * 84 | * @param progress 85 | * 取值范围0-100 86 | */ 87 | public void setCurProgress(int progress) { 88 | this.mSubCurProgress = progress; 89 | invalidate(); 90 | } 91 | 92 | public int getCurProgress() { 93 | return mSubCurProgress; 94 | } 95 | 96 | private class CircleAttribute { 97 | public boolean mBRoundPaintsFill = true; 98 | public Paint mBottomPaint; 99 | public Paint circlePaint; 100 | public int mDrawPos = -90; 101 | public int mSubPaintColor = foreColor; 102 | public int mBottomPaintColor = backColor; 103 | public int mPaintWidth = 0; 104 | public RectF mRoundOval = new RectF(); 105 | public RectF inRoundOval = new RectF(); 106 | public int mSidePaintInterval = 4; 107 | public Paint mSubPaint; 108 | 109 | public CircleAttribute() { 110 | this.mSubPaint = new Paint(); 111 | this.mSubPaint.setAntiAlias(true); 112 | this.mSubPaint.setStyle(Paint.Style.FILL); 113 | this.mSubPaint.setStrokeWidth(this.mPaintWidth); 114 | this.mSubPaint.setColor(this.mSubPaintColor); 115 | 116 | this.mBottomPaint = new Paint(); 117 | this.mBottomPaint.setAntiAlias(true); 118 | this.mBottomPaint.setStyle(Paint.Style.FILL); 119 | this.mBottomPaint.setStrokeWidth(this.mPaintWidth); 120 | this.mBottomPaint.setColor(this.mBottomPaintColor); 121 | 122 | this.circlePaint = new Paint(); 123 | this.circlePaint.setAntiAlias(true); 124 | this.circlePaint.setStyle(Paint.Style.FILL); 125 | this.circlePaint.setStrokeWidth(this.mPaintWidth); 126 | this.circlePaint.setColor(this.mBottomPaintColor); 127 | } 128 | 129 | public void autoFix(int width, int height) { 130 | int left = CircleProgress.this.getPaddingLeft(); 131 | int right = CircleProgress.this.getPaddingRight(); 132 | int top = CircleProgress.this.getPaddingTop(); 133 | int bottom = CircleProgress.this.getPaddingBottom(); 134 | this.mRoundOval.set(left + this.mPaintWidth / 2 + mSidePaintInterval, top + this.mPaintWidth / 2 135 | + mSidePaintInterval, width - right - this.mPaintWidth / 2 - mSidePaintInterval, height - bottom 136 | - this.mPaintWidth / 2 - mSidePaintInterval); 137 | this.inRoundOval.set(left + this.mPaintWidth / 2, top + this.mPaintWidth / 2, width - right 138 | - this.mPaintWidth / 2, height - bottom - this.mPaintWidth / 2); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/LinearRegression.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 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 java.lang.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; 48 | for (int i = 0; i < N; i++) sumx += x[i]; 49 | for (int i = 0; i < N; i++) { 50 | } 51 | for (int i = 0; i < N; i++) sumy += y[i]; 52 | double xbar = sumx / N; 53 | double ybar = sumy / N; 54 | 55 | // second pass: compute summary statistics 56 | double xxbar = 0.0, yybar = 0.0, xybar = 0.0; 57 | for (int i = 0; i < N; i++) { 58 | xxbar += (x[i] - xbar) * (x[i] - xbar); 59 | yybar += (y[i] - ybar) * (y[i] - ybar); 60 | xybar += (x[i] - xbar) * (y[i] - ybar); 61 | } 62 | beta = xybar / xxbar; 63 | alpha = ybar - beta * xbar; 64 | 65 | // more statistical analysis 66 | double rss = 0.0; // residual sum of squares 67 | double ssr = 0.0; // regression sum of squares 68 | for (int i = 0; i < N; i++) { 69 | double fit = beta*x[i] + alpha; 70 | rss += (fit - y[i]) * (fit - y[i]); 71 | ssr += (fit - ybar) * (fit - ybar); 72 | } 73 | 74 | int degreesOfFreedom = N-2; 75 | R2 = ssr / yybar; 76 | svar = rss / degreesOfFreedom; 77 | svar1 = svar / xxbar; 78 | svar0 = svar/N + xbar*xbar*svar1; 79 | } 80 | 81 | /** 82 | * Returns the y-intercept α of the best of the best-fit line y = α + β x. 83 | * @return the y-intercept α of the best-fit line y = α + β x 84 | */ 85 | public double intercept() { 86 | return alpha; 87 | } 88 | 89 | /** 90 | * Returns the slope β of the best of the best-fit line y = α + β x. 91 | * @return the slope β of the best-fit line y = α + β x 92 | */ 93 | public double slope() { 94 | return beta; 95 | } 96 | 97 | /** 98 | * Returns the coefficient of determination R2. 99 | * @return the coefficient of determination R2, which is a real number between 0 and 1 100 | */ 101 | public double R2() { 102 | return R2; 103 | } 104 | 105 | /** 106 | * Returns the standard error of the estimate for the intercept. 107 | * @return the standard error of the estimate for the intercept 108 | */ 109 | public double interceptStdErr() { 110 | return Math.sqrt(svar0); 111 | } 112 | 113 | /** 114 | * Returns the standard error of the estimate for the slope. 115 | * @return the standard error of the estimate for the slope 116 | */ 117 | public double slopeStdErr() { 118 | return Math.sqrt(svar1); 119 | } 120 | 121 | /** 122 | * Returns the expected response y given the value of the predictor 123 | * variable x. 124 | * @param x the value of the predictor variable 125 | * @return the expected response y given the value of the predictor 126 | * variable x 127 | */ 128 | public double predict(double x) { 129 | return beta*x + alpha; 130 | } 131 | 132 | /** 133 | * Returns a string representation of the simple linear regression model. 134 | * @return a string representation of the simple linear regression model, 135 | * including the best-fit line and the coefficient of determination R2 136 | */ 137 | public String toString() { 138 | String s = ""; 139 | s += String.format("%.2f N + %.2f", slope(), intercept()); 140 | return s + " (R^2 = " + String.format("%.3f", R2()) + ")"; 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/SwipeFlingAdapterView.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | import android.content.Context; 4 | import android.database.DataSetObserver; 5 | import android.util.AttributeSet; 6 | import android.view.Gravity; 7 | import android.view.View; 8 | import android.widget.Adapter; 9 | import android.widget.FrameLayout; 10 | 11 | import com.yiqivr.tinderswipe.widget.FlingCardListener.SWIPEMODE; 12 | 13 | public class SwipeFlingAdapterView extends BaseFlingAdapterView implements HelperFlingWithProportionListener { 14 | 15 | private Adapter mAdapter; 16 | private int maxVisible = 4; 17 | private int minAdapterStack = 6; 18 | private int lastObjectInStack = 0; 19 | private OnLeftRightFlingListener lrFlingListener; 20 | private OnTopBottomFlingListener tbFlingListener; 21 | private SWIPEMODE swipeMode = SWIPEMODE.LEFT_RIGHT; 22 | 23 | public SwipeFlingAdapterView(Context context) { 24 | super(context); 25 | } 26 | 27 | public SwipeFlingAdapterView(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | } 30 | 31 | public SwipeFlingAdapterView(Context context, AttributeSet attrs, int defStyle) { 32 | super(context, attrs, defStyle); 33 | } 34 | 35 | private final DataSetObserver mDataSetObserver = new DataSetObserver() { 36 | @Override 37 | public void onChanged() { 38 | observedChange(); 39 | } 40 | 41 | @Override 42 | public void onInvalidated() { 43 | observedChange(); 44 | } 45 | 46 | private void observedChange() { 47 | 48 | final int adapterCount = mAdapter.getCount(); 49 | 50 | if (adapterCount <= minAdapterStack) { 51 | if (swipeMode == SWIPEMODE.LEFT_RIGHT) { 52 | if (lrFlingListener != null) 53 | lrFlingListener.onAdapterAboutToEmpty(adapterCount); 54 | } else { 55 | if (tbFlingListener != null) 56 | tbFlingListener.onAdapterAboutToEmpty(adapterCount); 57 | } 58 | } 59 | 60 | if (adapterCount == 0) { 61 | removeAllViewsInLayout(); 62 | } else if (adapterCount == 1) { 63 | removeAllViewsInLayout(); 64 | layoutChildren(0); 65 | setTopView(); 66 | } else if (adapterCount <= maxVisible) { 67 | try { 68 | removeViewsInLayout(0, lastObjectInStack); 69 | } catch (NullPointerException e) { 70 | e.printStackTrace(); 71 | } 72 | layoutChildren(1); 73 | } 74 | } 75 | }; 76 | 77 | @Override 78 | public Adapter getAdapter() { 79 | return mAdapter; 80 | } 81 | 82 | @Override 83 | public void setAdapter(Adapter adapter) { 84 | if (mAdapter != null) { 85 | mAdapter.unregisterDataSetObserver(mDataSetObserver); 86 | } 87 | this.mAdapter = adapter; 88 | if (mAdapter != null) { 89 | mAdapter.registerDataSetObserver(mDataSetObserver); 90 | } 91 | requestLayout(); 92 | } 93 | 94 | @Override 95 | public void onCardExited() { 96 | if (swipeMode == SWIPEMODE.LEFT_RIGHT) { 97 | if (lrFlingListener != null) 98 | lrFlingListener.removeFirstObjectInAdapter(); 99 | } else { 100 | if (tbFlingListener != null) 101 | tbFlingListener.removeFirstObjectInAdapter(); 102 | } 103 | removeAllViewsInLayout(); 104 | layoutChildren(0); 105 | setTopView(); 106 | } 107 | 108 | @Override 109 | public void leftExit(Object dataObject) { 110 | if (lrFlingListener != null) { 111 | lrFlingListener.onLeftCardExit(dataObject); 112 | } 113 | } 114 | 115 | @Override 116 | public void rightExit(Object dataObject) { 117 | if (lrFlingListener != null) { 118 | lrFlingListener.onRightCardExit(dataObject); 119 | } 120 | } 121 | 122 | @Override 123 | public void topExit(Object dataObject) { 124 | if (tbFlingListener != null) { 125 | tbFlingListener.onTopCardExit(dataObject); 126 | } 127 | } 128 | 129 | @Override 130 | public void bottomExit(Object dataObject) { 131 | if (tbFlingListener != null) { 132 | tbFlingListener.onBottomCardExit(dataObject); 133 | } 134 | } 135 | 136 | public void setSwipeMode(SWIPEMODE swipeMode) { 137 | this.swipeMode = swipeMode; 138 | } 139 | 140 | public void setLeftRightFlingListener(OnLeftRightFlingListener onFlingListener) { 141 | this.lrFlingListener = onFlingListener; 142 | } 143 | 144 | public void setTopBottomFlingListener(OnTopBottomFlingListener tbFlingListener) { 145 | this.tbFlingListener = tbFlingListener; 146 | } 147 | 148 | @Override 149 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 150 | super.onLayout(changed, left, top, right, bottom); 151 | if (mAdapter == null) { 152 | return; 153 | } 154 | removeAllViewsInLayout(); 155 | layoutChildren(0); 156 | setTopView(); 157 | } 158 | 159 | private void layoutChildren(final int index) { 160 | int position = index; 161 | while (position < mAdapter.getCount() && position < maxVisible) { 162 | View newBottomChild = mAdapter.getView(position, null, this); 163 | if (newBottomChild.getVisibility() != GONE) { 164 | addAndMeasureChild(newBottomChild); 165 | lastObjectInStack = position; 166 | } 167 | position++; 168 | } 169 | 170 | positionItems(index); 171 | } 172 | 173 | private void addAndMeasureChild(View child) { 174 | 175 | FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams(); 176 | if (lp == null) { 177 | lp = new FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.WRAP_CONTENT, 178 | android.widget.FrameLayout.LayoutParams.WRAP_CONTENT); 179 | lp.gravity = Gravity.CENTER; 180 | } 181 | 182 | addViewInLayout(child, 0, lp, true); 183 | 184 | int childWidthSpec = getChildMeasureSpec(getWidthMeasureSpec(), getPaddingLeft() + getPaddingRight() 185 | + lp.leftMargin + lp.rightMargin, lp.width); 186 | 187 | int childHeightSpec = getChildMeasureSpec(getHeightMeasureSpec(), getPaddingTop() + getPaddingBottom() 188 | + lp.topMargin + lp.bottomMargin, lp.height); 189 | 190 | child.measure(childWidthSpec, childHeightSpec); 191 | } 192 | 193 | private void positionItems(int firstIteration) { 194 | for (int index = firstIteration; index < getChildCount(); index++) { 195 | View child = getChildAt(index); 196 | 197 | int width = child.getMeasuredWidth(); 198 | int height = child.getMeasuredHeight(); 199 | FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams(); 200 | 201 | int childLeft; 202 | int childTop; 203 | 204 | int gravity = lp.gravity; 205 | if (gravity == -1) { 206 | gravity = Gravity.TOP | Gravity.START; 207 | } 208 | 209 | final int layoutDirection = getLayoutDirection(); 210 | final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); 211 | final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; 212 | 213 | switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 214 | case Gravity.CENTER_HORIZONTAL: 215 | childLeft = (getWidth() + getPaddingLeft() - getPaddingRight() - width) / 2 + lp.leftMargin 216 | - lp.rightMargin; 217 | break; 218 | case Gravity.RIGHT: 219 | childLeft = getWidth() + getPaddingRight() - width - lp.rightMargin; 220 | break; 221 | case Gravity.LEFT: 222 | default: 223 | childLeft = getPaddingLeft() + lp.leftMargin; 224 | break; 225 | } 226 | 227 | switch (verticalGravity) { 228 | case Gravity.CENTER_VERTICAL: 229 | childTop = (getHeight() + getPaddingTop() - getPaddingBottom() - height) / 2 + lp.topMargin 230 | - lp.bottomMargin; 231 | break; 232 | case Gravity.BOTTOM: 233 | childTop = getHeight() - getPaddingBottom() - height - lp.bottomMargin; 234 | break; 235 | case Gravity.TOP: 236 | default: 237 | childTop = getPaddingTop() + lp.topMargin; 238 | break; 239 | } 240 | 241 | child.layout(childLeft, childTop, childLeft + width, childTop + height); 242 | } 243 | } 244 | 245 | private void setTopView() { 246 | if (getChildCount() > 0) { 247 | // Log.d("", "lastObjectInStack = " + lastObjectInStack); 248 | View tv = getChildAt(lastObjectInStack); 249 | View nextTv = null; 250 | if (getChildCount() > 1) { 251 | nextTv = getChildAt(lastObjectInStack - 1); 252 | } 253 | 254 | if (tv != null) { 255 | FlingCardListener flingCardListener = new FlingCardListener(swipeMode, tv, nextTv, getWidth(), 256 | getHeight(), tv.getX(), tv.getY(), tv.getHeight(), tv.getWidth(), mAdapter.getItem(0), this); 257 | if(bottomFinalPosX != 0 && bottomFinalPosY != 0) 258 | flingCardListener.setBottomFinalPos(bottomFinalPosX, bottomFinalPosY); 259 | tv.setOnTouchListener(flingCardListener); 260 | } 261 | } 262 | } 263 | 264 | private float bottomFinalPosX, bottomFinalPosY; 265 | 266 | public void setBottomFinalPos(float bottomFinalPosX, float bottomFinalPosY) { 267 | this.bottomFinalPosX = bottomFinalPosX; 268 | this.bottomFinalPosY = bottomFinalPosY; 269 | } 270 | 271 | public void setMaxVisible(int maxVisible) { 272 | this.maxVisible = maxVisible; 273 | } 274 | 275 | public void setMinStackInAdapter(int minAdapterStack) { 276 | this.minAdapterStack = minAdapterStack; 277 | } 278 | 279 | @Override 280 | public LayoutParams generateLayoutParams(AttributeSet attrs) { 281 | return new FrameLayout.LayoutParams(getContext(), attrs); 282 | } 283 | 284 | @Override 285 | public void flingOccurPercent(View selfView, int percent, boolean swipeTop) { 286 | if (tbFlingListener != null && tbFlingListener instanceof OnTopBottomFlingWithProportionListener) { 287 | ((OnTopBottomFlingWithProportionListener) tbFlingListener).onFlingSuccessPercent(selfView, percent, 288 | swipeTop); 289 | } 290 | } 291 | 292 | @Override 293 | public void flingResetOrigin(View selfView) { 294 | if (tbFlingListener != null && tbFlingListener instanceof OnTopBottomFlingWithProportionListener) { 295 | ((OnTopBottomFlingWithProportionListener) tbFlingListener).onFlingResetOrigin(selfView); 296 | } 297 | } 298 | 299 | } 300 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/BoxLayout.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | import android.animation.Animator; 4 | import android.animation.Animator.AnimatorListener; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ObjectAnimator; 7 | import android.content.Context; 8 | import android.util.AttributeSet; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.animation.AccelerateInterpolator; 12 | import android.view.animation.DecelerateInterpolator; 13 | import android.view.animation.OvershootInterpolator; 14 | import android.widget.LinearLayout; 15 | 16 | import com.yiqivr.tinderswipe.R; 17 | 18 | /** 19 | * @author lvning 20 | * @version create time:2014-10-17_下午4:17:03 21 | * @Description 收藏盒子 22 | */ 23 | public class BoxLayout extends LinearLayout { 24 | 25 | // 收藏单个动画 26 | public static final int STACK_INDEX_BACK = 0; 27 | public static final int STACK_INDEX_CENTER = 1; 28 | public static final int STACK_INDEX_FORE = 2; 29 | // 查看收藏动画 30 | public static final int ONE_COLLECT_ANIM = 3; 31 | public static final int TWO_COLLECT_ANIM = 4; 32 | public static final int MORE_COLLECT_ANIM = 5; 33 | // 收回收藏动画 34 | public static final int ONE_PAGER_ANIM = 6; 35 | 36 | public static final int TWO_PAGER_ANIM_LEFT = 7;// 两个,左边在中间 37 | public static final int TWO_PAGER_ANIM_RIGHT = 8;// 两个,右边在中间 38 | public static final int MORE_PAGER_ANIM_LEFT = 9;// 多个,左边在中间 39 | public static final int MORE_PAGER_ANIM_RIGHT = 10;// 多个,右边在中间 40 | 41 | public static final int MORE_PAGER_ANIM_OTHER = 11;// 其他 42 | 43 | private View foreView, centerView, backView; 44 | private boolean foreVisible, centerVisible, backVisible; 45 | 46 | private static final long ANIM_TIME = 600l; 47 | 48 | public BoxLayout(Context context, AttributeSet attrs) { 49 | super(context, attrs); 50 | init(context); 51 | } 52 | 53 | public BoxLayout(Context context) { 54 | super(context); 55 | init(context); 56 | } 57 | 58 | private void init(Context context) { 59 | LayoutInflater.from(context).inflate(R.layout.box_card_layout, this); 60 | foreView = findViewById(R.id.boxpic_out); 61 | centerView = findViewById(R.id.boxpic_out1); 62 | backView = findViewById(R.id.boxpic_out2); 63 | setGone(); 64 | } 65 | 66 | protected void setGone() { 67 | foreView.setAlpha(0); 68 | centerView.setAlpha(0); 69 | backView.setAlpha(0); 70 | } 71 | 72 | protected void setVisible() { 73 | if (foreVisible) 74 | foreView.setAlpha(1); 75 | if (centerVisible) 76 | centerView.setAlpha(1); 77 | if (backVisible) 78 | backView.setAlpha(1); 79 | } 80 | 81 | public void setForeImage(int resId) { 82 | foreView.setBackgroundResource(resId); 83 | if (foreView.getAlpha() == 0) { 84 | foreView.setAlpha(1); 85 | } 86 | startAnim(STACK_INDEX_FORE); 87 | } 88 | 89 | public void setCenterImage(int resId) { 90 | centerView.setBackgroundResource(resId); 91 | centerView.setAlpha(1); 92 | startAnim(STACK_INDEX_CENTER); 93 | } 94 | 95 | public void setBackImage(int resId) { 96 | backView.setBackgroundResource(resId); 97 | backView.setAlpha(1); 98 | startAnim(STACK_INDEX_BACK); 99 | } 100 | 101 | public float getChildTop() { 102 | return backView.getTop(); 103 | } 104 | 105 | public float getChildLeft() { 106 | return backView.getLeft(); 107 | } 108 | 109 | private void startAnim(int index) { 110 | View v = null; 111 | switch (index) { 112 | case STACK_INDEX_BACK: 113 | v = backView; 114 | break; 115 | case STACK_INDEX_CENTER: 116 | v = centerView; 117 | break; 118 | case STACK_INDEX_FORE: 119 | v = foreView; 120 | break; 121 | default: 122 | break; 123 | } 124 | ObjectAnimator transAnim = ObjectAnimator.ofFloat(v, "translationY", 150f, 0f); 125 | transAnim.setInterpolator(new OvershootInterpolator()); 126 | transAnim.setDuration(250); 127 | transAnim.start(); 128 | } 129 | 130 | public void setReadyPullIn(int animType, float transByY, final float backTransByX, final Runnable endAction) { 131 | setVisible(); 132 | ObjectAnimator transAnim = ObjectAnimator.ofFloat(this, "translationY", transByY, 0); 133 | transAnim.setDuration(ANIM_TIME).setInterpolator(new DecelerateInterpolator()); 134 | 135 | ObjectAnimator centerRotateAnim = ObjectAnimator.ofFloat(centerView, "rotation", 0f, -5); 136 | centerRotateAnim.setDuration(ANIM_TIME); 137 | 138 | ObjectAnimator foreRotateAnim = ObjectAnimator.ofFloat(foreView, "rotation", 0f, -5); 139 | foreRotateAnim.setDuration(ANIM_TIME); 140 | 141 | ObjectAnimator centerTransAnim = ObjectAnimator.ofFloat(centerView, "translationX", backTransByX, 0); 142 | centerTransAnim.setDuration(ANIM_TIME).setInterpolator(new OvershootInterpolator()); 143 | 144 | ObjectAnimator backTransAnim = ObjectAnimator.ofFloat(backView, "translationX", backTransByX, 0); 145 | backTransAnim.setDuration(ANIM_TIME).setInterpolator(new OvershootInterpolator()); 146 | 147 | ObjectAnimator backTransAnim2 = ObjectAnimator.ofFloat(backView, "translationX", 2 * backTransByX, 0); 148 | backTransAnim2.setDuration(ANIM_TIME).setInterpolator(new OvershootInterpolator()); 149 | 150 | ObjectAnimator centerTransAnimN1 = ObjectAnimator.ofFloat(centerView, "translationX", -backTransByX, 0); 151 | centerTransAnimN1.setDuration(ANIM_TIME).setInterpolator(new OvershootInterpolator()); 152 | 153 | ObjectAnimator foreTransAnimN2 = ObjectAnimator.ofFloat(foreView, "translationX", -2 * backTransByX, 0); 154 | foreTransAnimN2.setDuration(ANIM_TIME).setInterpolator(new OvershootInterpolator()); 155 | 156 | ObjectAnimator foreTransAnimN1 = ObjectAnimator.ofFloat(foreView, "translationX", -backTransByX, 0); 157 | foreTransAnimN1.setDuration(ANIM_TIME).setInterpolator(new OvershootInterpolator()); 158 | 159 | AnimatorSet set = new AnimatorSet(); 160 | switch (animType) { 161 | case ONE_PAGER_ANIM: 162 | set.play(transAnim); 163 | break; 164 | case TWO_PAGER_ANIM_LEFT: 165 | set.play(transAnim).before(centerRotateAnim).before(backTransAnim); 166 | break; 167 | case TWO_PAGER_ANIM_RIGHT: 168 | centerView.setTranslationX(-backTransByX); 169 | backView.setTranslationX(0); 170 | set.play(centerTransAnimN1).with(centerRotateAnim).after(transAnim); 171 | break; 172 | case MORE_PAGER_ANIM_LEFT: 173 | set.play(transAnim).before(foreRotateAnim).before(centerTransAnim).before(centerRotateAnim) 174 | .before(backTransAnim2); 175 | break; 176 | case MORE_PAGER_ANIM_RIGHT: 177 | foreView.setTranslationX(-2 * backTransByX); 178 | centerView.setTranslationX(-backTransByX); 179 | backView.setTranslationX(0); 180 | set.play(foreTransAnimN2).with(foreRotateAnim).with(centerTransAnimN1).with(centerRotateAnim) 181 | .after(transAnim); 182 | break; 183 | case MORE_PAGER_ANIM_OTHER: 184 | foreView.setTranslationX(-backTransByX); 185 | centerView.setTranslationX(0); 186 | backView.setTranslationX(backTransByX); 187 | set.play(transAnim).before(foreTransAnimN1).before(foreRotateAnim).before(backTransAnim) 188 | .before(centerRotateAnim); 189 | break; 190 | default: 191 | break; 192 | } 193 | set.start(); 194 | set.addListener(new AnimatorListener() { 195 | 196 | @Override 197 | public void onAnimationStart(Animator animation) { 198 | 199 | } 200 | 201 | @Override 202 | public void onAnimationRepeat(Animator animation) { 203 | 204 | } 205 | 206 | @Override 207 | public void onAnimationEnd(Animator animation) { 208 | endAction.run(); 209 | 210 | } 211 | 212 | @Override 213 | public void onAnimationCancel(Animator animation) { 214 | 215 | } 216 | }); 217 | } 218 | 219 | public void popOut(int animType, float transByY, final float backTransByX, final Runnable endAction) { 220 | ObjectAnimator transAnim = ObjectAnimator.ofFloat(this, "translationY", 0, transByY); 221 | transAnim.setDuration(ANIM_TIME).setInterpolator(new OvershootInterpolator()); 222 | 223 | ObjectAnimator centerRotateAnim = ObjectAnimator.ofFloat(centerView, "rotation", -5, 0f); 224 | centerRotateAnim.setDuration(ANIM_TIME); 225 | 226 | ObjectAnimator foreRotateAnim = ObjectAnimator.ofFloat(foreView, "rotation", -5, 0f); 227 | foreRotateAnim.setDuration(ANIM_TIME); 228 | 229 | ObjectAnimator centerTransAnim = ObjectAnimator.ofFloat(centerView, "translationX", 0, backTransByX); 230 | centerTransAnim.setDuration(ANIM_TIME).setInterpolator(new AccelerateInterpolator()); 231 | 232 | ObjectAnimator backTransAnim = ObjectAnimator.ofFloat(backView, "translationX", 0, backTransByX); 233 | backTransAnim.setDuration(ANIM_TIME).setInterpolator(new AccelerateInterpolator()); 234 | 235 | ObjectAnimator backTransAnim2 = ObjectAnimator.ofFloat(backView, "translationX", 0, 2 * backTransByX); 236 | backTransAnim2.setDuration(ANIM_TIME).setInterpolator(new AccelerateInterpolator()); 237 | 238 | AnimatorSet set = new AnimatorSet(); 239 | switch (animType) { 240 | case ONE_COLLECT_ANIM: 241 | set.play(transAnim); 242 | backVisible = true; 243 | centerVisible = false; 244 | foreVisible = false; 245 | break; 246 | case TWO_COLLECT_ANIM: 247 | set.play(transAnim).with(centerRotateAnim).before(backTransAnim); 248 | backVisible = true; 249 | centerVisible = true; 250 | foreVisible = false; 251 | break; 252 | case MORE_COLLECT_ANIM: 253 | set.play(transAnim).with(centerRotateAnim).with(foreRotateAnim).before(backTransAnim2) 254 | .before(centerTransAnim); 255 | backVisible = true; 256 | centerVisible = true; 257 | foreVisible = true; 258 | break; 259 | default: 260 | break; 261 | } 262 | set.start(); 263 | set.addListener(new AnimatorListener() { 264 | 265 | @Override 266 | public void onAnimationStart(Animator animation) { 267 | 268 | } 269 | 270 | @Override 271 | public void onAnimationRepeat(Animator animation) { 272 | 273 | } 274 | 275 | @Override 276 | public void onAnimationEnd(Animator animation) { 277 | setGone(); 278 | endAction.run(); 279 | } 280 | 281 | @Override 282 | public void onAnimationCancel(Animator animation) { 283 | 284 | } 285 | }); 286 | } 287 | 288 | } 289 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/widget/FlingCardListener.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe.widget; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.util.Log; 6 | import android.view.MotionEvent; 7 | import android.view.VelocityTracker; 8 | import android.view.View; 9 | import android.view.animation.AccelerateInterpolator; 10 | import android.view.animation.OvershootInterpolator; 11 | 12 | public class FlingCardListener implements View.OnTouchListener { 13 | 14 | private final float originalX; 15 | private final float originalY; 16 | private final int originalHeight; 17 | private final int originalWidth; 18 | private final int halfWidth, halfHeight; 19 | private final int parentWidth, parentHeight; 20 | private final HelperFlingWithProportionListener helperFlingListener; 21 | private final Object dataObject; 22 | private float BASE_ROTATION_DEGREES = 15.f; 23 | 24 | private float aPosX; 25 | private float aPosY; 26 | private float aDownTouchX; 27 | private float aDownTouchY; 28 | private static final int INVALID_POINTER_ID = -1; 29 | 30 | private int mActivePointerId = INVALID_POINTER_ID; 31 | private View frame = null; 32 | private View nextFrame = null; 33 | 34 | private final int TOUCH_ABOVE = 0; 35 | private final int TOUCH_BELOW = 1; 36 | private final int TOUCH_LEFT = 2; 37 | private final int TOUCH_RIGHT = 3; 38 | private int touchPosition; 39 | 40 | public enum SWIPEMODE { 41 | LEFT_RIGHT, UP_DOWN 42 | } 43 | 44 | private SWIPEMODE swipeMode; 45 | 46 | private VelocityTracker vTracker; 47 | private boolean velocityReach = false; 48 | 49 | private float bottomFinalPosX, bottomFinalPosY; 50 | 51 | public FlingCardListener(SWIPEMODE swipeMode, View frame, View nextFrame, int parentWidth, int parentHeight, 52 | float originalX, float originalY, int originalHeight, int originalWidth, Object itemAtPosition, 53 | HelperFlingWithProportionListener helperFlingListener) { 54 | super(); 55 | this.swipeMode = swipeMode; 56 | this.frame = frame; 57 | this.nextFrame = nextFrame; 58 | this.parentWidth = parentWidth; 59 | this.parentHeight = parentHeight; 60 | this.originalX = originalX; 61 | this.originalY = originalY; 62 | this.originalHeight = originalHeight; 63 | this.originalWidth = originalWidth; 64 | this.halfWidth = this.originalWidth / 2; 65 | this.halfHeight = this.originalHeight / 2; 66 | this.dataObject = itemAtPosition; 67 | this.helperFlingListener = helperFlingListener; 68 | } 69 | 70 | @Override 71 | public boolean onTouch(View view, MotionEvent event) { 72 | 73 | switch (event.getAction() & MotionEvent.ACTION_MASK) { 74 | case MotionEvent.ACTION_DOWN: 75 | 76 | mActivePointerId = event.getPointerId(0); 77 | final float x = event.getX(mActivePointerId); 78 | final float y = event.getY(mActivePointerId); 79 | 80 | aDownTouchX = x; 81 | aDownTouchY = y; 82 | if (aPosX == 0) { 83 | aPosX = frame.getX(); 84 | } 85 | if (aPosY == 0) { 86 | aPosY = frame.getY(); 87 | } 88 | 89 | if (swipeMode == SWIPEMODE.LEFT_RIGHT) { 90 | if (y < originalHeight / 2) { 91 | touchPosition = TOUCH_ABOVE; 92 | } else { 93 | touchPosition = TOUCH_BELOW; 94 | } 95 | } else { 96 | if (x < originalWidth / 2) { 97 | touchPosition = TOUCH_LEFT; 98 | } else { 99 | touchPosition = TOUCH_RIGHT; 100 | } 101 | } 102 | 103 | if (vTracker == null) { 104 | vTracker = VelocityTracker.obtain(); 105 | } else { 106 | vTracker.clear(); 107 | } 108 | vTracker.addMovement(event); 109 | 110 | break; 111 | 112 | case MotionEvent.ACTION_UP: 113 | mActivePointerId = INVALID_POINTER_ID; 114 | if (velocityReach) { 115 | helperFlingListener.flingOccurPercent(frame, 100, false); 116 | bottomSelectedEvent(); 117 | } else { 118 | resetCardViewOnStack(); 119 | } 120 | try { 121 | vTracker.recycle(); 122 | } catch (Exception e) { 123 | e.printStackTrace(); 124 | } 125 | velocityReach = false; 126 | break; 127 | 128 | case MotionEvent.ACTION_POINTER_DOWN: 129 | break; 130 | 131 | case MotionEvent.ACTION_POINTER_UP: 132 | // Extract the index of the pointer that left the touch sensor 133 | final int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; 134 | final int pointerId = event.getPointerId(pointerIndex); 135 | if (pointerId == mActivePointerId) { 136 | // This was our active pointer going up. Choose a new 137 | // active pointer and adjust accordingly. 138 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 139 | mActivePointerId = event.getPointerId(newPointerIndex); 140 | } 141 | break; 142 | case MotionEvent.ACTION_MOVE: 143 | 144 | // Find the index of the active pointer and fetch its position 145 | final int pointerIndexMove = event.findPointerIndex(mActivePointerId); 146 | final float xMove = event.getX(pointerIndexMove); 147 | final float yMove = event.getY(pointerIndexMove); 148 | 149 | // Calculate the distance moved 150 | final float dx = xMove - aDownTouchX; 151 | final float dy = yMove - aDownTouchY; 152 | 153 | // Move the frame 154 | aPosX += dx; 155 | aPosY += dy; 156 | 157 | // calculate the rotation degrees 158 | float distOriginalX = aPosX - originalX; 159 | float distOriginalY = aPosY - originalY; 160 | // float rotation = BASE_ROTATION_DEGREES * 2.f * distOriginalX / parentWidth; 161 | // if (touchPosition == TOUCH_BELOW) { 162 | // rotation = -rotation; 163 | // } 164 | 165 | // Log.v("", "distOriginalX = " + distOriginalX); 166 | // Log.e("", "distOriginalY = " + distOriginalY); 167 | 168 | double moveDis = Math.sqrt(Math.pow(distOriginalX, 2) + Math.pow(distOriginalY, 2)); 169 | 170 | float scalePercent = (float) (moveDis / (originalHeight / 2.5)); 171 | // Log.d("", "scalePercent = " + scalePercent); 172 | if (scalePercent > 1) 173 | scalePercent = 1; 174 | setUpNextFrame(scalePercent, false); 175 | 176 | // no rotation for a while. 177 | // float degree = (float) (Math.atan((aPosY - originalY) / (aPosX - originalX)) / Math.PI * 180); 178 | // Log.d("", "degree = " + degree + ", distOriginalX = " + distOriginalX); 179 | // float result; 180 | // if (degree >= -90 && degree < -45) { 181 | // result = degree + 90; 182 | // } else if (degree >= 45 && degree <= 90) { 183 | // result = 90 - degree; 184 | // } else { 185 | // result = degree; 186 | // } 187 | 188 | frame.setX(aPosX); 189 | frame.setY(aPosY); 190 | 191 | helperFlingListener.flingOccurPercent(frame, getMoveDisPercent(), distOriginalY < 0); 192 | 193 | vTracker.addMovement(event); 194 | vTracker.computeCurrentVelocity(100); 195 | float yVelocity = vTracker.getYVelocity(); 196 | if (yVelocity > 550f) { 197 | velocityReach = true; 198 | } 199 | break; 200 | 201 | case MotionEvent.ACTION_CANCEL: { 202 | mActivePointerId = INVALID_POINTER_ID; 203 | try { 204 | vTracker.recycle(); 205 | } catch (Exception e) { 206 | e.printStackTrace(); 207 | } 208 | velocityReach = false; 209 | break; 210 | } 211 | } 212 | return true; 213 | } 214 | 215 | protected void setUpNextFrame(float scalePercent, boolean withAnim) { 216 | if (nextFrame == null) 217 | return; 218 | if (!withAnim) { 219 | nextFrame.setScaleX(scalePercent); 220 | nextFrame.setScaleY(scalePercent); 221 | nextFrame.setAlpha(scalePercent); 222 | } else { 223 | nextFrame.animate().scaleX(scalePercent).scaleY(scalePercent).alpha(scalePercent).setDuration(50).start(); 224 | } 225 | } 226 | 227 | private void resetCardViewOnStack() { 228 | float distOriginalY = aPosY - originalY; 229 | if (swipeMode == SWIPEMODE.LEFT_RIGHT) { 230 | if (aPosX + halfWidth > rightBorder()) { 231 | setUpNextFrame(1f, true); 232 | onRightSelected(); 233 | } else if (aPosX + halfWidth < leftBorder()) { 234 | setUpNextFrame(1f, true); 235 | onLeftSelected(); 236 | } else { 237 | resetOrigin(); 238 | } 239 | } else { 240 | if (distOriginalY > originalHeight / 2) { 241 | bottomSelectedEvent(); 242 | } else if (distOriginalY < 0 && Math.abs(distOriginalY) > originalHeight / 2) { 243 | setUpNextFrame(1f, true); 244 | onTopSelected(); 245 | } else { 246 | resetOrigin(); 247 | } 248 | } 249 | } 250 | 251 | protected void bottomSelectedEvent() { 252 | setUpNextFrame(1f, true); 253 | onBottomSelected(); 254 | } 255 | 256 | public int getMoveDisPercent() { 257 | return (int) (Math.min(1f, Math.abs((aPosY - originalY)) / (originalHeight / 2)) * 100); 258 | } 259 | 260 | protected void resetOrigin() { 261 | aPosX = 0; 262 | aPosY = 0; 263 | aDownTouchX = 0; 264 | aDownTouchY = 0; 265 | frame.animate().setDuration(100).setInterpolator(new OvershootInterpolator()).x(originalX).y(originalY) 266 | .rotation(0); 267 | helperFlingListener.flingResetOrigin(frame); 268 | } 269 | 270 | public float leftBorder() { 271 | return parentWidth / 4.f; 272 | } 273 | 274 | public float rightBorder() { 275 | return 3 * parentWidth / 4.f; 276 | } 277 | 278 | public void setRotationDegrees(float degrees) { 279 | this.BASE_ROTATION_DEGREES = degrees; 280 | } 281 | 282 | public void onLeftSelected() { 283 | this.frame.animate().setDuration(100).setInterpolator(new AccelerateInterpolator()).x(-originalWidth) 284 | .y(getExitPoint(-originalWidth)).setListener(new AnimatorListenerAdapter() { 285 | @Override 286 | public void onAnimationEnd(Animator animation) { 287 | helperFlingListener.onCardExited(); 288 | helperFlingListener.leftExit(dataObject); 289 | } 290 | }).rotation(-getExitRotation()); 291 | } 292 | 293 | public void onTopSelected() { 294 | this.frame.animate().setDuration(100).setInterpolator(new AccelerateInterpolator()).x(originalX) 295 | .y(-originalHeight).setListener(new AnimatorListenerAdapter() { 296 | @Override 297 | public void onAnimationEnd(Animator animation) { 298 | helperFlingListener.onCardExited(); 299 | helperFlingListener.topExit(dataObject); 300 | } 301 | }); 302 | } 303 | 304 | public void onRightSelected() { 305 | this.frame.animate().setDuration(100).setInterpolator(new AccelerateInterpolator()).x(parentWidth) 306 | .y(getExitPoint(parentWidth)).setListener(new AnimatorListenerAdapter() { 307 | @Override 308 | public void onAnimationEnd(Animator animation) { 309 | helperFlingListener.onCardExited(); 310 | helperFlingListener.rightExit(dataObject); 311 | } 312 | }).rotation(getExitRotation()); 313 | } 314 | 315 | public void setBottomFinalPos(float bottomFinalPosX, float bottomFinalPosY) { 316 | this.bottomFinalPosX = bottomFinalPosX; 317 | this.bottomFinalPosY = bottomFinalPosY; 318 | } 319 | 320 | public void onBottomSelected() { 321 | this.frame.animate().setDuration(100).setInterpolator(new AccelerateInterpolator()) 322 | .x(bottomFinalPosX == 0 ? originalX : bottomFinalPosX) 323 | .y(bottomFinalPosY == 0 ? 3 * originalHeight : bottomFinalPosY) 324 | .setListener(new AnimatorListenerAdapter() { 325 | @Override 326 | public void onAnimationEnd(Animator animation) { 327 | 328 | helperFlingListener.onCardExited(); 329 | helperFlingListener.bottomExit(dataObject); 330 | } 331 | }); 332 | } 333 | 334 | private float getExitPoint(int exitXPoint) { 335 | float[] x = new float[2]; 336 | x[0] = originalX; 337 | x[1] = aPosX; 338 | 339 | float[] y = new float[2]; 340 | y[0] = originalY; 341 | y[1] = aPosY; 342 | 343 | LinearRegression regression = new LinearRegression(x, y); 344 | 345 | float result = (float) regression.slope() * exitXPoint + (float) regression.intercept(); 346 | Log.e("", "getExitPoint = " + result); 347 | 348 | return result; 349 | } 350 | 351 | private float getExitRotation() { 352 | float rotation = BASE_ROTATION_DEGREES * 2.f * (parentWidth - originalX) / parentWidth; 353 | if (touchPosition == TOUCH_BELOW) { 354 | rotation = -rotation; 355 | } 356 | return rotation; 357 | } 358 | 359 | } 360 | -------------------------------------------------------------------------------- /src/com/yiqivr/tinderswipe/MyActivity.java: -------------------------------------------------------------------------------- 1 | package com.yiqivr.tinderswipe; 2 | 3 | import java.util.ArrayList; 4 | 5 | import android.annotation.SuppressLint; 6 | import android.app.Activity; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.support.v4.view.PagerAdapter; 10 | import android.support.v4.view.ViewPager; 11 | import android.util.Log; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.View.OnClickListener; 15 | import android.view.ViewGroup; 16 | import android.widget.BaseAdapter; 17 | import android.widget.TextView; 18 | import android.widget.Toast; 19 | 20 | import com.yiqivr.tinderswipe.widget.BoxLayout; 21 | import com.yiqivr.tinderswipe.widget.CircleProgress; 22 | import com.yiqivr.tinderswipe.widget.FlingCardListener.SWIPEMODE; 23 | import com.yiqivr.tinderswipe.widget.OnLeftRightFlingListener; 24 | import com.yiqivr.tinderswipe.widget.OnTopBottomFlingWithProportionListener; 25 | import com.yiqivr.tinderswipe.widget.SwipeFlingAdapterView; 26 | 27 | public class MyActivity extends Activity implements OnLeftRightFlingListener, OnTopBottomFlingWithProportionListener { 28 | 29 | private ArrayList al; 30 | private ArrayList pagerView; 31 | private MyAdapter adapter; 32 | private MyPagerAdapter pagerAdapter; 33 | private BoxLayout boxLayout; 34 | 35 | private boolean addMoreExcuting; 36 | 37 | private ViewPager pager; 38 | private View shadow; 39 | private final int duration = 500; 40 | private final int OutInduration = 500; 41 | 42 | private float transByY, transByX; 43 | private View box; 44 | private SwipeFlingAdapterView flingContainer; 45 | 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_my); 50 | 51 | flingContainer = (SwipeFlingAdapterView) findViewById(R.id.frame); 52 | boxLayout = (BoxLayout) findViewById(R.id.boxlayout); 53 | pager = (ViewPager) findViewById(R.id.viewpager); 54 | shadow = findViewById(R.id.shadow); 55 | box = findViewById(R.id.collect_box); 56 | 57 | al = new ArrayList(); 58 | pagerView = new ArrayList(); 59 | for (int i = 1; i < 10; i++) { 60 | al.add(i + ""); 61 | } 62 | 63 | flingContainer.setSwipeMode(SWIPEMODE.UP_DOWN); 64 | flingContainer.setMaxVisible(2); 65 | flingContainer.setTopBottomFlingListener(this); 66 | // flingContainer.setLeftRightFlingListener(this); 67 | adapter = new MyAdapter(); 68 | flingContainer.setAdapter(adapter); 69 | 70 | pager.setVisibility(View.GONE); 71 | 72 | box.setOnClickListener(boxClickListener); 73 | 74 | shadow.setOnClickListener(shadowClickListener); 75 | 76 | } 77 | 78 | private OnClickListener boxClickListener = new View.OnClickListener() { 79 | 80 | @Override 81 | public void onClick(View v) { 82 | if (pagerView.size() == 0) { 83 | return; 84 | } 85 | box.setOnClickListener(null); 86 | pager.setAlpha(0); 87 | shadow.setAlpha(0); 88 | shadow.setVisibility(View.VISIBLE); 89 | pager.setVisibility(View.VISIBLE); 90 | pagerAdapter = new MyPagerAdapter(); 91 | pager.setPageMargin(-200); 92 | pager.setAdapter(pagerAdapter); 93 | 94 | boxLayout.bringToFront(); 95 | box.bringToFront(); 96 | 97 | pager.post(new Runnable() { 98 | 99 | @Override 100 | public void run() { 101 | transByY = pager.getTop() - boxLayout.getTop() - boxLayout.getChildTop(); 102 | int collectItemsSize = pagerView.size(); 103 | int animType = BoxLayout.ONE_COLLECT_ANIM; 104 | switch (collectItemsSize) { 105 | case 1: 106 | animType = BoxLayout.ONE_COLLECT_ANIM; 107 | break; 108 | case 2: 109 | animType = BoxLayout.TWO_COLLECT_ANIM; 110 | transByX = pager.getChildAt(1).getLeft() - pager.getChildAt(0).getLeft(); 111 | break; 112 | default: 113 | animType = BoxLayout.MORE_COLLECT_ANIM; 114 | transByX = pager.getChildAt(1).getLeft() - pager.getChildAt(0).getLeft(); 115 | break; 116 | } 117 | 118 | boxLayout.popOut(animType, transByY, transByX, new Runnable() { 119 | 120 | @Override 121 | public void run() { 122 | flingContainer.bringToFront(); 123 | shadow.bringToFront(); 124 | pager.setAlpha(1); 125 | pager.bringToFront(); 126 | box.bringToFront(); 127 | findViewById(R.id.rl_content).requestLayout(); 128 | shadow.setOnClickListener(shadowClickListener); 129 | pager.setOnClickListener(shadowClickListener); 130 | } 131 | }); 132 | 133 | shadow.animate().alpha(1).setDuration(2 * OutInduration); 134 | } 135 | }); 136 | 137 | } 138 | }; 139 | 140 | private OnClickListener shadowClickListener = new View.OnClickListener() { 141 | 142 | @SuppressLint("NewApi") 143 | @Override 144 | public void onClick(View v) { 145 | shadow.setOnClickListener(null); 146 | shadow.animate().alpha(0).setDuration(2 * duration).withEndAction(new Runnable() { 147 | 148 | @Override 149 | public void run() { 150 | shadow.setVisibility(View.GONE); 151 | } 152 | }); 153 | Log.e("", "pager.getChildCount() = " + pager.getChildCount()); 154 | 155 | int animType = BoxLayout.MORE_PAGER_ANIM_LEFT; 156 | int pagerChildCount = pager.getChildCount(); 157 | pager.setVisibility(View.GONE); 158 | shadow.bringToFront(); 159 | boxLayout.bringToFront(); 160 | box.bringToFront(); 161 | switch (pagerChildCount) { 162 | case 1: 163 | animType = BoxLayout.ONE_PAGER_ANIM; 164 | break; 165 | case 2: 166 | boolean leftCenter = (pager.getCurrentItem() == 0); 167 | if (pagerView.size() > 2) { 168 | animType = leftCenter ? BoxLayout.MORE_PAGER_ANIM_LEFT : BoxLayout.MORE_PAGER_ANIM_RIGHT; 169 | } else { 170 | animType = leftCenter ? BoxLayout.TWO_PAGER_ANIM_LEFT : BoxLayout.TWO_PAGER_ANIM_RIGHT; 171 | } 172 | break; 173 | default: 174 | animType = BoxLayout.MORE_PAGER_ANIM_OTHER; 175 | break; 176 | } 177 | boxLayout.setReadyPullIn(animType, transByY, transByX, new Runnable() { 178 | 179 | @Override 180 | public void run() { 181 | boxLayout.bringToFront(); 182 | flingContainer.bringToFront(); 183 | shadow.bringToFront(); 184 | box.bringToFront(); 185 | findViewById(R.id.rl_content).requestLayout(); 186 | box.setOnClickListener(boxClickListener); 187 | } 188 | }); 189 | 190 | } 191 | 192 | }; 193 | 194 | @Override 195 | public void removeFirstObjectInAdapter() { 196 | Log.d("LIST", "removed object!"); 197 | al.remove(0); 198 | adapter.notifyDataSetChanged(); 199 | } 200 | 201 | @Override 202 | public void onLeftCardExit(Object dataObject) { 203 | Toast.makeText(this, "Left!" + dataObject.toString(), Toast.LENGTH_SHORT).show(); 204 | } 205 | 206 | @Override 207 | public void onRightCardExit(Object dataObject) { 208 | Toast.makeText(this, "Right!" + dataObject.toString(), Toast.LENGTH_SHORT).show(); 209 | } 210 | 211 | @Override 212 | public void onAdapterAboutToEmpty(int itemsInAdapter) { 213 | // Ask for more data here 214 | System.out.println("Almost empty!!! itemsInAdapter = " + itemsInAdapter); 215 | netWorkSimulation(); 216 | } 217 | 218 | private void netWorkSimulation() { 219 | if (addMoreExcuting) 220 | return; 221 | addMoreExcuting = true; 222 | new Handler().postDelayed(new Runnable() { 223 | 224 | @Override 225 | public void run() { 226 | ArrayList moreData = new ArrayList(); 227 | for (int i = 10; i < 20; i++) { 228 | moreData.add(i + ""); 229 | } 230 | 231 | al.addAll(moreData); 232 | adapter.notifyDataSetChanged(); 233 | addMoreExcuting = false; 234 | } 235 | }, 1000); 236 | } 237 | 238 | @Override 239 | public void onTopCardExit(Object dataObject) { 240 | } 241 | 242 | @Override 243 | public void onBottomCardExit(Object dataObject) { 244 | if (pagerView.size() == 0) { 245 | boxLayout 246 | .setBackImage(Integer.valueOf(dataObject.toString()) % 2 == 0 ? R.drawable.card : R.drawable.card2); 247 | } else if (pagerView.size() == 1) { 248 | boxLayout.setCenterImage(Integer.valueOf(dataObject.toString()) % 2 == 0 ? R.drawable.card 249 | : R.drawable.card2); 250 | } else { 251 | boxLayout 252 | .setForeImage(Integer.valueOf(dataObject.toString()) % 2 == 0 ? R.drawable.card : R.drawable.card2); 253 | } 254 | View v = LayoutInflater.from(MyActivity.this).inflate(R.layout.no_circle_item, null); 255 | TextView tv = (TextView) v.findViewById(R.id.helloText); 256 | tv.setText(dataObject.toString()); 257 | pagerView.add(v); 258 | if (pagerAdapter != null) 259 | pagerAdapter.notifyDataSetChanged(); 260 | } 261 | 262 | private class MyAdapter extends BaseAdapter { 263 | 264 | @Override 265 | public int getCount() { 266 | // TODO Auto-generated method stub 267 | return al.size(); 268 | } 269 | 270 | @Override 271 | public Object getItem(int position) { 272 | // TODO Auto-generated method stub 273 | return al.get(position); 274 | } 275 | 276 | @Override 277 | public long getItemId(int position) { 278 | // TODO Auto-generated method stub 279 | return 0; 280 | } 281 | 282 | @Override 283 | public View getView(int position, View convertView, ViewGroup parent) { 284 | Log.v("", "getView--------"); 285 | View v = LayoutInflater.from(MyActivity.this).inflate(R.layout.item, null); 286 | TextView tv = (TextView) v.findViewById(R.id.helloText); 287 | tv.setBackgroundResource(Integer.valueOf(al.get(position)) % 2 == 0 ? R.drawable.card : R.drawable.card2); 288 | tv.setText(al.get(position)); 289 | CircleProgress top = (CircleProgress) v.findViewById(R.id.top_progress); 290 | CircleProgress bottom = (CircleProgress) v.findViewById(R.id.bottom_progress); 291 | top.setTag(0); 292 | bottom.setTag(0); 293 | top.setAlpha(0); 294 | bottom.setAlpha(0); 295 | return v; 296 | } 297 | 298 | } 299 | 300 | private class MyPagerAdapter extends PagerAdapter { 301 | 302 | @Override 303 | public int getCount() { 304 | return pagerView.size(); 305 | } 306 | 307 | @Override 308 | public boolean isViewFromObject(View arg0, Object arg1) { 309 | return arg0 == arg1; 310 | } 311 | 312 | @Override 313 | public void destroyItem(ViewGroup container, int position, Object object) { 314 | container.removeView(pagerView.get(position)); 315 | } 316 | 317 | @Override 318 | public Object instantiateItem(ViewGroup container, int position) { 319 | container.addView(pagerView.get(position)); 320 | return pagerView.get(position); 321 | } 322 | 323 | } 324 | 325 | @Override 326 | public void onFlingSuccessPercent(View selfView, int percent, boolean flingTop) { 327 | CircleProgress top = (CircleProgress) selfView.findViewById(R.id.top_progress); 328 | CircleProgress bottom = (CircleProgress) selfView.findViewById(R.id.bottom_progress); 329 | if (top != null && bottom != null) { 330 | if (flingTop) { 331 | if ((Integer) top.getTag() == 0) { 332 | top.setTag(1); 333 | top.animate().alpha(1).setDuration(500l); 334 | } 335 | if ((Integer) bottom.getTag() == 1) { 336 | bottom.setTag(0); 337 | bottom.setCurProgress(0); 338 | bottom.animate().alpha(0).setDuration(500l); 339 | } 340 | top.setCurProgress(percent); 341 | } else { 342 | if ((Integer) top.getTag() == 1) { 343 | top.setTag(0); 344 | top.setCurProgress(0); 345 | top.animate().alpha(0).setDuration(500l); 346 | } 347 | if ((Integer) bottom.getTag() == 0) { 348 | bottom.setTag(1); 349 | bottom.animate().alpha(1).setDuration(500l); 350 | } 351 | bottom.setCurProgress(percent); 352 | } 353 | } 354 | 355 | } 356 | 357 | @Override 358 | public void onFlingResetOrigin(View selfView) { 359 | CircleProgress top = (CircleProgress) selfView.findViewById(R.id.top_progress); 360 | CircleProgress bottom = (CircleProgress) selfView.findViewById(R.id.bottom_progress); 361 | if ((Integer) top.getTag() == 1) { 362 | top.setTag(0); 363 | top.animate().alpha(0).setDuration(500l); 364 | } 365 | if ((Integer) bottom.getTag() == 1) { 366 | bottom.setTag(0); 367 | bottom.animate().alpha(0).setDuration(500l); 368 | } 369 | } 370 | 371 | } 372 | --------------------------------------------------------------------------------