借鉴 :https://github.com/xiepeijie/SwipeCardView package me.payge.swipecardview; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckedTextView; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.lorentzos.flingswipe.SwipeFlingAdapterView; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Random; public class MainActivity extends AppCompatActivity implements SwipeFlingAdapterView.onFlingListener, SwipeFlingAdapterView.OnItemClickListener, View.OnClickListener { int[] headerIcons = { R.drawable.i1, R.drawable.i2, R.drawable.i3, R.drawable.i4, R.drawable.i5, R.drawable.i6 }; String[] names = {"张三", "李四", "王五", "小明", "小红", "小花"}; String[] citys = {"北京", "上海", "广州", "深圳"}; String[] edus = {"大专", "本科", "硕士", "博士"}; String[] years = {"1年", "2年", "3年", "4年", "5年"}; Random ran = new Random(); private int cardWidth; private int cardHeight; private SwipeFlingAdapterView swipeView; private InnerAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); loadData(); } private void initView() { DisplayMetrics dm = getResources().getDisplayMetrics(); float density = dm.density; cardWidth = (int) (dm.widthPixels - (2 * 18 * density)); cardHeight = (int) (dm.heightPixels - (338 * density)); swipeView = (SwipeFlingAdapterView) findViewById(R.id.swipe_view); if (swipeView != null) { swipeView.setIsNeedSwipe(true); swipeView.setFlingListener(this); swipeView.setOnItemClickListener(this); adapter = new InnerAdapter(); swipeView.setAdapter(adapter); } View v = findViewById(R.id.swipeLeft); if (v != null) { v.setOnClickListener(this); } v = findViewById(R.id.swipeRight); if (v != null) { v.setOnClickListener(this); } } @Override public void onItemClicked(MotionEvent event, View v, Object dataObject) { Toast.makeText(this,"ddd",Toast.LENGTH_SHORT).show(); } @Override public void removeFirstObjectInAdapter() { adapter.remove(0); } @Override public void onLeftCardExit(Object dataObject) { Toast.makeText(this,"往左滑",Toast.LENGTH_SHORT).show(); } @Override public void onRightCardExit(Object dataObject) { Toast.makeText(this,"往右滑",Toast.LENGTH_SHORT).show(); } @Override public void onAdapterAboutToEmpty(int itemsInAdapter) { if (itemsInAdapter == 3) { // loadData(); Toast.makeText(this,"没啦",Toast.LENGTH_SHORT).show(); } } @Override public void onScroll(float progress, float scrollXProgress) { // Toast.makeText(this,"大模大样d",Toast.LENGTH_SHORT).show(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.swipeLeft: swipeView.swipeLeft(); //swipeView.swipeLeft(250); break; case R.id.swipeRight: swipeView.swipeRight(); //swipeView.swipeRight(250); } } private void loadData() { new AsyncTask<Void, Void, List<Talent>>() { @Override protected List<Talent> doInBackground(Void... params) { ArrayList<Talent> list = new ArrayList<>(10); Talent talent; for (int i = 0; i < 10; i++) { talent = new Talent(); talent.headerIcon = headerIcons[i % headerIcons.length]; talent.nickname = names[ran.nextInt(names.length - 1)]; talent.cityName = citys[ran.nextInt(citys.length - 1)]; talent.educationName = edus[ran.nextInt(edus.length - 1)]; talent.workYearName = years[ran.nextInt(years.length - 1)]; list.add(talent); } return list; } @Override protected void onPostExecute(List<Talent> list) { super.onPostExecute(list); adapter.addAll(list); } }.execute(); } private class InnerAdapter extends BaseAdapter { ArrayList<Talent> objs; public InnerAdapter() { objs = new ArrayList<>(); } public void addAll(Collection<Talent> collection) { if (isEmpty()) { objs.addAll(collection); notifyDataSetChanged(); } else { objs.addAll(collection); } } public void clear() { objs.clear(); notifyDataSetChanged(); } public boolean isEmpty() { return objs.isEmpty(); } public void remove(int index) { if (index > -1 && index < objs.size()) { objs.remove(index); notifyDataSetChanged(); } } @Override public int getCount() { return objs.size(); } @Override public Talent getItem(int position) { if (objs == null || objs.size() == 0) return null; return objs.get(position); } @Override public long getItemId(int position) { return position; } // TODO: getView @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; Talent talent = getItem(position); if (convertView == null) { convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_new_item, parent, false); holder = new ViewHolder(); convertView.setTag(holder); convertView.getLayoutParams().width = cardWidth; holder.portraitView = (ImageView) convertView.findViewById(R.id.portrait); //holder.portraitView.getLayoutParams().width = cardWidth; holder.portraitView.getLayoutParams().height = cardHeight; holder.nameView = (TextView) convertView.findViewById(R.id.name); //parentView.getLayoutParams().width = cardWidth; //holder.jobView = (TextView) convertView.findViewById(R.id.job); //holder.companyView = (TextView) convertView.findViewById(R.id.company); holder.cityView = (TextView) convertView.findViewById(R.id.city); holder.eduView = (TextView) convertView.findViewById(R.id.education); holder.workView = (TextView) convertView.findViewById(R.id.work_year); } else { holder = (ViewHolder) convertView.getTag(); } holder.portraitView.setImageResource(talent.headerIcon); holder.nameView.setText(String.format("%s", talent.nickname)); //holder.jobView.setText(talent.jobName); final CharSequence no = "暂无"; holder.cityView.setHint(no); holder.cityView.setText(talent.cityName); holder.cityView.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.home01_icon_location, 0, 0); holder.eduView.setHint(no); holder.eduView.setText(talent.educationName); holder.eduView.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.home01_icon_edu, 0, 0); holder.workView.setHint(no); holder.workView.setText(talent.workYearName); holder.workView.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.home01_icon_work_year, 0, 0); return convertView; } } private static class ViewHolder { ImageView portraitView; TextView nameView; TextView cityView; TextView eduView; TextView workView; CheckedTextView collectView; } public static class Talent { public int headerIcon; public String nickname; public String cityName; public String educationName; public String workYearName; } } 2.package com.lorentzos.flingswipe; import android.content.Context; import android.util.AttributeSet; import android.widget.AdapterView; /** * Created by dionysis_lorentzos on 6/8/14 * for package com.lorentzos.swipecards * and project Swipe cards. * Use with caution dinausaurs might appear! */ abstract class BaseFlingAdapterView extends AdapterView { private int heightMeasureSpec; private int widthMeasureSpec; public BaseFlingAdapterView(Context context) { super(context); } public BaseFlingAdapterView(Context context, AttributeSet attrs) { super(context, attrs); } public BaseFlingAdapterView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void setSelection(int i) { throw new UnsupportedOperationException("Not supported"); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); this.widthMeasureSpec = widthMeasureSpec; this.heightMeasureSpec = heightMeasureSpec; } public int getWidthMeasureSpec() { return widthMeasureSpec; } public int getHeightMeasureSpec() { return heightMeasureSpec; } }
3.package com.lorentzos.flingswipe; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.database.DataSetObserver; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.widget.Adapter; import android.widget.FrameLayout; import java.util.ArrayList; import me.payge.swipecardview.R; /** * Created by dionysis_lorentzos on 5/8/14 * for package com.lorentzos.swipecards * and project Swipe cards. * Use with caution dinosaurs might appear! */ public class SwipeFlingAdapterView extends BaseFlingAdapterView { private ArrayList<View> cacheItems = new ArrayList<>(); //缩放层叠效果 private int yOffsetStep; // view叠加垂直偏移量的步长 private static final float SCALE_STEP = 0.08f; // view叠加缩放的步长 //缩放层叠效果 private int MAX_VISIBLE = 4; // 值建议最小为4 private int MIN_ADAPTER_STACK = 6; private float ROTATION_DEGREES = 2f; private int LAST_OBJECT_IN_STACK = 0; private Adapter mAdapter; private onFlingListener mFlingListener; private AdapterDataSetObserver mDataSetObserver; private boolean mInLayout = false; private View mActiveCard = null; private OnItemClickListener mOnItemClickListener; private FlingCardListener flingCardListener; // 支持左右滑 public boolean isNeedSwipe = true; private int initTop; private int initLeft; public SwipeFlingAdapterView(Context context) { this(context, null); } public SwipeFlingAdapterView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwipeFlingAdapterView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeFlingAdapterView, defStyle, 0); MAX_VISIBLE = a.getInt(R.styleable.SwipeFlingAdapterView_max_visible, MAX_VISIBLE); MIN_ADAPTER_STACK = a.getInt(R.styleable.SwipeFlingAdapterView_min_adapter_stack, MIN_ADAPTER_STACK); ROTATION_DEGREES = a.getFloat(R.styleable.SwipeFlingAdapterView_rotation_degrees, ROTATION_DEGREES); yOffsetStep = a.getDimensionPixelOffset(R.styleable.SwipeFlingAdapterView_y_offset_step, 0); a.recycle(); } public void setIsNeedSwipe(boolean isNeedSwipe) { this.isNeedSwipe = isNeedSwipe; } /** * A shortcut method to set both the listeners and the adapter. * * @param context The activity context which extends onFlingListener, OnItemClickListener or both * @param mAdapter The adapter you have to set. */ public void init(final Context context, Adapter mAdapter) { if(context instanceof onFlingListener) { mFlingListener = (onFlingListener) context; }else{ throw new RuntimeException("Activity does not implement SwipeFlingAdapterView.onFlingListener"); } if(context instanceof OnItemClickListener){ mOnItemClickListener = (OnItemClickListener) context; } setAdapter(mAdapter); } @Override public View getSelectedView() { return mActiveCard; } @Override public void requestLayout() { if (!mInLayout) { super.requestLayout(); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); // if we don't have an adapter, we don't need to do anything if (mAdapter == null) { return; } mInLayout = true; final int adapterCount = mAdapter.getCount(); if (adapterCount == 0) { // removeAllViewsInLayout(); removeAndAddToCache(0); } else { View topCard = getChildAt(LAST_OBJECT_IN_STACK); if(mActiveCard != null && topCard != null && topCard == mActiveCard) { // removeViewsInLayout(0, LAST_OBJECT_IN_STACK); removeAndAddToCache(1); layoutChildren(1, adapterCount); }else{ // Reset the UI and set top view listener // removeAllViewsInLayout(); removeAndAddToCache(0); layoutChildren(0, adapterCount); setTopView(); } } mInLayout = false; if (initTop == 0 && initLeft == 0 && mActiveCard != null) { initTop = mActiveCard.getTop(); initLeft = mActiveCard.getLeft(); } if(adapterCount < MIN_ADAPTER_STACK) { if(mFlingListener != null){ mFlingListener.onAdapterAboutToEmpty(adapterCount); } } } private void removeAndAddToCache(int remain) { View view; for (int i = 0; i < getChildCount() - remain; ) { view = getChildAt(i); removeViewInLayout(view); cacheItems.add(view); } } private void layoutChildren(int startingIndex, int adapterCount){ while (startingIndex < Math.min(adapterCount, MAX_VISIBLE) ) { View item = null; if (cacheItems.size() > 0) { item = cacheItems.get(0); cacheItems.remove(item); } View newUnderChild = mAdapter.getView(startingIndex, item, this); if (newUnderChild.getVisibility() != GONE) { makeAndAddView(newUnderChild, startingIndex); LAST_OBJECT_IN_STACK = startingIndex; } startingIndex++; } } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private void makeAndAddView(View child, int index) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams(); addViewInLayout(child, 0, lp, true); final boolean needToMeasure = child.isLayoutRequested(); if (needToMeasure) { int childWidthSpec = getChildMeasureSpec(getWidthMeasureSpec(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, lp.width); int childHeightSpec = getChildMeasureSpec(getHeightMeasureSpec(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, lp.height); child.measure(childWidthSpec, childHeightSpec); } else { cleanupLayoutState(child); } int w = child.getMeasuredWidth(); int h = child.getMeasuredHeight(); int gravity = lp.gravity; if (gravity == -1) { gravity = Gravity.TOP | Gravity.START; } int layoutDirection = 0; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; int childLeft; int childTop; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = (getWidth() + getPaddingLeft() - getPaddingRight() - w) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.END: childLeft = getWidth() + getPaddingRight() - w - lp.rightMargin; break; case Gravity.START: default: childLeft = getPaddingLeft() + lp.leftMargin; break; } switch (verticalGravity) { case Gravity.CENTER_VERTICAL: childTop = (getHeight() + getPaddingTop() - getPaddingBottom() - h) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = getHeight() - getPaddingBottom() - h - lp.bottomMargin; break; case Gravity.TOP: default: childTop = getPaddingTop() + lp.topMargin; break; } child.layout(childLeft, childTop, childLeft + w, childTop + h); // 缩放层叠效果 adjustChildView(child, index); } private void adjustChildView(View child, int index) { if (index > -1 && index < MAX_VISIBLE) { int multiple; if (index > 2) multiple = 2; else multiple = index; child.offsetTopAndBottom(yOffsetStep * multiple); child.setScaleX(1 - SCALE_STEP * multiple); child.setScaleY(1 - SCALE_STEP * multiple); } } private void adjustChildrenOfUnderTopView(float scrollRate) { int count = getChildCount(); if (count > 1) { int i; int multiple; if (count == 2) { i = LAST_OBJECT_IN_STACK - 1; multiple = 1; } else { i = LAST_OBJECT_IN_STACK - 2; multiple = 2; } float rate = Math.abs(scrollRate); for (; i < LAST_OBJECT_IN_STACK; i++, multiple--) { View underTopView = getChildAt(i); int offset = (int) (yOffsetStep * (multiple - rate)); underTopView.offsetTopAndBottom(offset - underTopView.getTop() + initTop); underTopView.setScaleX(1 - SCALE_STEP * multiple + SCALE_STEP * rate); underTopView.setScaleY(1 - SCALE_STEP * multiple + SCALE_STEP * rate); } } } /** * Set the top view and add the fling listener */ private void setTopView() { if(getChildCount()>0){ mActiveCard = getChildAt(LAST_OBJECT_IN_STACK); if(mActiveCard != null && mFlingListener != null) { flingCardListener = new FlingCardListener(mActiveCard, mAdapter.getItem(0), ROTATION_DEGREES, new FlingCardListener.FlingListener() { @Override public void onCardExited() { removeViewInLayout(mActiveCard); mActiveCard = null; mFlingListener.removeFirstObjectInAdapter(); } @Override public void leftExit(Object dataObject) { mFlingListener.onLeftCardExit(dataObject); } @Override public void rightExit(Object dataObject) { mFlingListener.onRightCardExit(dataObject); } @Override public void onClick(MotionEvent event, View v, Object dataObject) { if(mOnItemClickListener != null) mOnItemClickListener.onItemClicked(event, v, dataObject); } @Override public void onScroll(float progress, float scrollXProgress) { // Log.e("Log", "onScroll " + progress); adjustChildrenOfUnderTopView(progress); mFlingListener.onScroll(progress, scrollXProgress); } }); // 设置是否支持左右滑 flingCardListener.setIsNeedSwipe(isNeedSwipe); mActiveCard.setOnTouchListener(flingCardListener); } } } public FlingCardListener getTopCardListener() throws NullPointerException { if(flingCardListener==null){ throw new NullPointerException("flingCardListener is null"); } return flingCardListener; } public void setMaxVisible(int MAX_VISIBLE){ this.MAX_VISIBLE = MAX_VISIBLE; } public void setMinStackInAdapter(int MIN_ADAPTER_STACK) { this.MIN_ADAPTER_STACK = MIN_ADAPTER_STACK; } /** * click to swipe left */ public void swipeLeft() { getTopCardListener().selectLeft(); } public void swipeLeft(int duration) { getTopCardListener().selectLeft(duration); } /** * click to swipe right */ public void swipeRight() { getTopCardListener().selectRight(); } public void swipeRight(int duration) { getTopCardListener().selectRight(duration); } @Override public Adapter getAdapter() { return mAdapter; } @Override public void setAdapter(Adapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); mDataSetObserver = null; } mAdapter = adapter; if (mAdapter != null && mDataSetObserver == null) { mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); } } public void setFlingListener(onFlingListener onFlingListener) { this.mFlingListener = onFlingListener; } public void setOnItemClickListener(OnItemClickListener onItemClickListener){ this.mOnItemClickListener = onItemClickListener; } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new FrameLayout.LayoutParams(getContext(), attrs); } private class AdapterDataSetObserver extends DataSetObserver { @Override public void onChanged() { requestLayout(); } @Override public void onInvalidated() { requestLayout(); } } public interface OnItemClickListener { void onItemClicked(MotionEvent event, View v, Object dataObject); } public interface onFlingListener { void removeFirstObjectInAdapter(); void onLeftCardExit(Object dataObject); void onRightCardExit(Object dataObject); void onAdapterAboutToEmpty(int itemsInAdapter); void onScroll(float progress, float scrollXProgress); } }package com.lorentzos.flingswipe; /************************************************************************* * Compilation: javac LinearRegression.java * Execution: java LinearRegression * * Compute least squares solution to y = beta * x + alpha. * Simple linear regression. * *************************************************************************/ /** * The <tt>LinearRegression</tt> class performs a simple linear regression * on an set of <em>N</em> data points (<em>y<sub>i</sub></em>, <em>x<sub>i</sub></em>). * That is, it fits a straight line <em>y</em> = α + β <em>x</em>, * (where <em>y</em> is the response variable, <em>x</em> is the predictor variable, * α is the <em>y-intercept</em>, and β is the <em>slope</em>) * that minimizes the sum of squared residuals of the linear regression model. * It also computes associated statistics, including the coefficient of * determination <em>R</em><sup>2</sup> and the standard deviation of the * estimates for the slope and <em>y</em>-intercept. * * @author Robert Sedgewick * @author Kevin Wayne */ public class LinearRegression { private final int N; private final double alpha, beta; private final double R2; private final double svar, svar0, svar1; /** * Performs a linear regression on the data points <tt>(y[i], x[i])</tt>. * @param x the values of the predictor variable * @param y the corresponding values of the response variable * @throws IllegalArgumentException if the lengths of the two arrays are not equal */ public LinearRegression(float[] x, float[] y) { if (x.length != y.length) { throw new IllegalArgumentException("array lengths are not equal"); } N = x.length; // first pass double sumx = 0.0, sumy = 0.0, sumx2 = 0.0; for (int i = 0; i < N; i++) sumx += x[i]; for (int i = 0; i < N; i++) sumx2 += x[i]*x[i]; for (int i = 0; i < N; i++) sumy += y[i]; double xbar = sumx / N; double ybar = sumy / N; // second pass: compute summary statistics double xxbar = 0.0, yybar = 0.0, xybar = 0.0; for (int i = 0; i < N; i++) { xxbar += (x[i] - xbar) * (x[i] - xbar); yybar += (y[i] - ybar) * (y[i] - ybar); xybar += (x[i] - xbar) * (y[i] - ybar); } beta = xybar / xxbar; alpha = ybar - beta * xbar; // more statistical analysis double rss = 0.0; // residual sum of squares double ssr = 0.0; // regression sum of squares for (int i = 0; i < N; i++) { double fit = beta*x[i] + alpha; rss += (fit - y[i]) * (fit - y[i]); ssr += (fit - ybar) * (fit - ybar); } int degreesOfFreedom = N-2; R2 = ssr / yybar; svar = rss / degreesOfFreedom; svar1 = svar / xxbar; svar0 = svar/N + xbar*xbar*svar1; } /** * Returns the <em>y</em>-intercept α of the best of the best-fit line <em>y</em> = α + β <em>x</em>. * @return the <em>y</em>-intercept α of the best-fit line <em>y = α + β x</em> */ public double intercept() { return alpha; } /** * Returns the slope β of the best of the best-fit line <em>y</em> = α + β <em>x</em>. * @return the slope β of the best-fit line <em>y</em> = α + β <em>x</em> */ public double slope() { return beta; } /** * Returns the coefficient of determination <em>R</em><sup>2</sup>. * @return the coefficient of determination <em>R</em><sup>2</sup>, which is a real number between 0 and 1 */ public double R2() { return R2; } /** * Returns the standard error of the estimate for the intercept. * @return the standard error of the estimate for the intercept */ public double interceptStdErr() { return Math.sqrt(svar0); } /** * Returns the standard error of the estimate for the slope. * @return the standard error of the estimate for the slope */ public double slopeStdErr() { return Math.sqrt(svar1); } /** * Returns the expected response <tt>y</tt> given the value of the predictor * variable <tt>x</tt>. * @param x the value of the predictor variable * @return the expected response <tt>y</tt> given the value of the predictor * variable <tt>x</tt> */ public double predict(double x) { return beta*x + alpha; } /** * Returns a string representation of the simple linear regression model. * @return a string representation of the simple linear regression model, * including the best-fit line and the coefficient of determination <em>R</em><sup>2</sup> */ public String toString() { String s = ""; s += String.format("%.2f N + %.2f", slope(), intercept()); return s + " (R^2 = " + String.format("%.3f", R2()) + ")"; } }package com.lorentzos.flingswipe; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.LinearInterpolator; import android.view.animation.OvershootInterpolator; /** * Created by dionysis_lorentzos on 5/8/14 * for package com.lorentzos.swipecards * and project Swipe cards. * Use with caution dinausaurs might appear! */ public class FlingCardListener implements View.OnTouchListener { private final float objectX; private final float objectY; private final int objectH; private final int objectW; private final int parentWidth; private final FlingListener mFlingListener; private final Object dataObject; private final float halfWidth; private float BASE_ROTATION_DEGREES; private float aPosX; private float aPosY; private float aDownTouchX; private float aDownTouchY; private static final int INVALID_POINTER_ID = -1; // The active pointer is the one currently moving our object. private int mActivePointerId = INVALID_POINTER_ID; private View frame = null; private final int TOUCH_ABOVE = 0; private final int TOUCH_BELOW = 1; private int touchPosition; // private final Object obj = new Object(); private boolean isAnimationRunning = false; private float MAX_COS = (float) Math.cos(Math.toRadians(45)); // 支持左右滑 private boolean isNeedSwipe = true; private float aTouchUpX; private int animDuration = 300; private float scale; /** * every time we touch down,we should stop the {@link #animRun} */ private boolean resetAnimCanceled = false; public FlingCardListener(View frame, Object itemAtPosition, float rotation_degrees, FlingListener flingListener) { super(); this.frame = frame; this.objectX = frame.getX(); this.objectY = frame.getY(); this.objectW = frame.getWidth(); this.objectH = frame.getHeight(); this.halfWidth = objectW/2f; this.dataObject = itemAtPosition; this.parentWidth = ((ViewGroup) frame.getParent()).getWidth(); this.BASE_ROTATION_DEGREES = rotation_degrees; this.mFlingListener = flingListener; } public void setIsNeedSwipe(boolean isNeedSwipe) { this.isNeedSwipe = isNeedSwipe; } @Override public boolean onTouch(View view, MotionEvent event) { try { switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: // remove the listener because 'onAnimationEnd' will still be called if we cancel the animation. this.frame.animate().setListener(null); this.frame.animate().cancel(); resetAnimCanceled = true; // Save the ID of this pointer mActivePointerId = event.getPointerId(0); final float x = event.getX(mActivePointerId); final float y = event.getY(mActivePointerId); // Remember where we started aDownTouchX = x; aDownTouchY = y; // to prevent an initial jump of the magnifier, aposX and aPosY must // have the values from the magnifier frame aPosX = frame.getX(); aPosY = frame.getY(); if (y < objectH/2) { touchPosition = TOUCH_ABOVE; } else { touchPosition = TOUCH_BELOW; } break; case MotionEvent.ACTION_POINTER_DOWN: break; case MotionEvent.ACTION_POINTER_UP: // Extract the index of the pointer that left the touch sensor final int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; final int pointerId = event.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mActivePointerId = event.getPointerId(newPointerIndex); } break; case MotionEvent.ACTION_MOVE: // Find the index of the active pointer and fetch its position final int pointerIndexMove = event.findPointerIndex(mActivePointerId); final float xMove = event.getX(pointerIndexMove); final float yMove = event.getY(pointerIndexMove); // from http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html // Calculate the distance moved final float dx = xMove - aDownTouchX; final float dy = yMove - aDownTouchY; // Move the frame aPosX += dx; aPosY += dy; // calculate the rotation degrees float distObjectX = aPosX - objectX; float rotation = BASE_ROTATION_DEGREES * 2f * distObjectX / parentWidth; if (touchPosition == TOUCH_BELOW) { rotation = -rotation; } // in this area would be code for doing something with the view as the frame moves. if (isNeedSwipe) { frame.setX(aPosX); frame.setY(aPosY); frame.setRotation(rotation); mFlingListener.onScroll(getScrollProgress(), getScrollXProgressPercent()); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: //mActivePointerId = INVALID_POINTER_ID; int pointerCount = event.getPointerCount(); int activePointerId = Math.min(mActivePointerId, pointerCount - 1); aTouchUpX = event.getX(activePointerId); mActivePointerId = INVALID_POINTER_ID; resetCardViewOnStack(event); break; } } catch (Exception e) { e.printStackTrace(); } return true; } private float getScrollProgress() { float dx = aPosX - objectX; float dy = aPosY - objectY; float dis = Math.abs(dx) + Math.abs(dy); return Math.min(dis, 400f) / 400f; } private float getScrollXProgressPercent() { if (movedBeyondLeftBorder()) { return -1f; } else if (movedBeyondRightBorder()) { return 1f; } else { float zeroToOneValue = (aPosX + halfWidth - leftBorder()) / (rightBorder() - leftBorder()); return zeroToOneValue * 2f - 1f; } } private boolean resetCardViewOnStack(MotionEvent event) { if (isNeedSwipe) { final int duration = 200; if(movedBeyondLeftBorder()){ // Left Swipe onSelected(true, getExitPoint(-objectW), duration); mFlingListener.onScroll(1f, -1.0f); }else if(movedBeyondRightBorder()) { // Right Swipe onSelected(false, getExitPoint(parentWidth), duration); mFlingListener.onScroll(1f, 1.0f); }else{ float absMoveXDistance = Math.abs(aPosX-objectX); float absMoveYDistance = Math.abs(aPosY-objectY); if(absMoveXDistance < 4 && absMoveYDistance < 4){ mFlingListener.onClick(event, frame, dataObject); }else{ frame.animate() .setDuration(animDuration) .setInterpolator(new OvershootInterpolator(1.5f)) .x(objectX) .y(objectY) .rotation(0) .start(); scale = getScrollProgress(); this.frame.postDelayed(animRun, 0); resetAnimCanceled = false; } aPosX = 0; aPosY = 0; aDownTouchX = 0; aDownTouchY = 0; } } else { float distanceX = Math.abs(aTouchUpX - aDownTouchX); if(distanceX < 4) mFlingListener.onClick(event, frame, dataObject); } return false; } private Runnable animRun = new Runnable() { @Override public void run() { mFlingListener.onScroll(scale, 0); if (scale > 0 && !resetAnimCanceled) { scale = scale - 0.1f; if (scale < 0) scale = 0; frame.postDelayed(this, animDuration / 20); } } }; private boolean movedBeyondLeftBorder() { return aPosX+halfWidth < leftBorder(); } private boolean movedBeyondRightBorder() { return aPosX+halfWidth > rightBorder(); } public float leftBorder(){ return parentWidth/4f; } public float rightBorder(){ return 3*parentWidth/4f; } public void onSelected(final boolean isLeft, float exitY, long duration){ isAnimationRunning = true; float exitX; if(isLeft) { exitX = -objectW-getRotationWidthOffset(); }else { exitX = parentWidth+getRotationWidthOffset(); } this.frame.animate() .setDuration(duration) .setInterpolator(new LinearInterpolator()) .translationX(exitX) .translationY(exitY) //.rotation(isLeft ? -BASE_ROTATION_DEGREES:BASE_ROTATION_DEGREES) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (isLeft) { mFlingListener.onCardExited(); mFlingListener.leftExit(dataObject); } else { mFlingListener.onCardExited(); mFlingListener.rightExit(dataObject); } isAnimationRunning = false; } }).start(); } /** * Starts a default left exit animation. */ public void selectLeft(){ if(!isAnimationRunning) selectLeft(animDuration); } /** * Starts a default left exit animation. */ public void selectLeft(long duration){ if(!isAnimationRunning) onSelected(true, objectY, duration); } /** * Starts a default right exit animation. */ public void selectRight(){ if(!isAnimationRunning) selectRight(animDuration); } /** * Starts a default right exit animation. */ public void selectRight(long duration){ if(!isAnimationRunning) onSelected(false, objectY, duration); } private float getExitPoint(int exitXPoint) { float[] x = new float[2]; x[0] = objectX; x[1] = aPosX; float[] y = new float[2]; y[0] = objectY; y[1] = aPosY; LinearRegression regression = new LinearRegression(x,y); //Your typical y = ax+b linear regression return (float) regression.slope() * exitXPoint + (float) regression.intercept(); } private float getExitRotation(boolean isLeft){ float rotation = BASE_ROTATION_DEGREES * 2f * (parentWidth - objectX)/parentWidth; if (touchPosition == TOUCH_BELOW) { rotation = -rotation; } if(isLeft){ rotation = -rotation; } return rotation; } /** * When the object rotates it's width becomes bigger. * The maximum width is at 45 degrees. * * The below method calculates the width offset of the rotation. * */ private float getRotationWidthOffset() { return objectW/MAX_COS - objectW; } public void setRotationDegrees(float degrees) { this.BASE_ROTATION_DEGREES = degrees; } protected interface FlingListener { void onCardExited(); void leftExit(Object dataObject); void rightExit(Object dataObject); void onClick(MotionEvent event, View v, Object dataObject); void onScroll(float progress, float scrollXProgress); } }<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:swipe="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="me.payge.swipecardview.MainActivity"> <com.lorentzos.flingswipe.SwipeFlingAdapterView android:id="@+id/swipe_view" android:layout_width="match_parent" android:layout_height="match_parent" swipe:min_adapter_stack="4" swipe:max_visible="4" swipe:y_offset_step="28dp"/> <View android:id="@+id/anchor" android:layout_width="1dp" android:layout_height="50dp" android:layout_alignParentBottom="true" android:layout_centerInParent="true" android:layout_marginBottom="32dp"/> <Button android:id="@+id/swipeLeft" android:layout_width="60dp" android:layout_height="60dp" android:background="@drawable/bg_button" android:layout_alignTop="@id/anchor" android:layout_toLeftOf="@id/anchor" android:layout_marginRight="56dp" android:textColor="#ffffff" android:text="left"/> <Button android:id="@+id/swipeRight" android:layout_width="60dp" android:layout_height="60dp" android:background="@drawable/bg_button" android:layout_alignTop="@id/anchor" android:layout_toRightOf="@id/anchor" android:layout_marginLeft="56dp" android:textColor="#ffffff" android:text="right"/> </RelativeLayout><?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_gravity="center_horizontal" android:layout_marginTop="18dp" android:layout_marginLeft="18dp" android:layout_marginRight="18dp" android:background="@drawable/home01_bg_card"> <ImageView android:contentDescription="@null" android:id="@+id/portrait" android:layout_width="match_parent" android:layout_height="300dp" android:scaleType="centerCrop"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="100dp" android:orientation="vertical" android:layout_gravity="center_horizontal" android:padding="12dp" > <TextView android:id="@+id/name" android:layout_centerHorizontal="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="2dp" android:textColor="#565656" android:textSize="16sp" android:text=""/> <TextView android:id="@+id/education" android:layout_below="@id/name" android:layout_centerHorizontal="true" android:layout_marginTop="8dp" android:layout_marginLeft="36dp" android:layout_marginRight="36dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#565656" android:textSize="14sp" android:maxLines="1" android:text=""/> <TextView android:id="@+id/city" android:layout_alignTop="@id/education" android:layout_toLeftOf="@id/education" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#565656" android:textSize="14sp" android:maxLines="1" android:text=""/> <TextView android:id="@+id/work_year" android:layout_alignTop="@id/education" android:layout_toRightOf="@id/education" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#565656" android:textSize="14sp" android:maxLines="1" android:text=""/> </RelativeLayout> </LinearLayout><?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="SwipeFlingAdapterView"> <attr name="y_offset_step" format="dimension" /> <attr name="rotation_degrees" format="float" /> <attr name="min_adapter_stack" format="integer" /> <attr name="max_visible" format="integer" /> </declare-styleable> </resources>
卡片左右滑,类似探探效果。目前花椒也引用此效果方式
最新推荐文章于 2022-09-06 09:45:34 发布