package com.wenchao.cardstack;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import java.util.ArrayList;
public class CardStack extends RelativeLayout {
private boolean mEnableRotation;
private int mGravity;
private int mColor = -1;
private int mIndex = 0;
private int mNumVisible = 4;
private boolean canSwipe = true;
private ArrayAdapter<?> mAdapter;
private OnTouchListener mOnTouchListener;
private CardAnimator mCardAnimator;
private boolean mEnableLoop; // 是否允许循环滚动
private CardEventListener mEventListener = new DefaultStackEventListener(300);
private int mContentResource = 0;
private int mMargin;
public interface CardEventListener {
//section
// 0 | 1
//--------
// 2 | 3
// swipe distance, most likely be used with height and width of a view ;
boolean swipeEnd(int section, float distance);
boolean swipeStart(int section, float distance);
boolean swipeContinue(int section, float distanceX, float distanceY);
void discarded(int mIndex, int direction);
void topCardTapped();
}
public void discardTop(final int direction) {
mCardAnimator.discard(direction, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator arg0) {
mCardAnimator.initLayout();
mIndex++;
loadLast();
viewCollection.get(0).setOnTouchListener(null);
viewCollection.get(viewCollection.size() - 1).setOnTouchListener(mOnTouchListener);
mEventListener.discarded(mIndex - 1, direction);
}
});
}
/**
* 设置方向,支持上、下。
* 设置后调用{@link #reset(boolean)} 来重新初始化布局
*
* @param gravity {@link CardAnimator#TOP} 向上 {@link CardAnimator#BOTTOM} 向下,默认值
*/
public void setStackGravity(int gravity) {
mGravity = gravity;
}
/**
* 获取当前方向
*
* @return
*/
public int getStackGravity() {
return mGravity;
}
/**
* 是否允许旋转
* <p/>
* 设置后调用{@link #reset(boolean)} 来重新初始化布局
*
* @param enableRotation
*/
public void setEnableRotation(boolean enableRotation) {
mEnableRotation = enableRotation;
}
/**
* 是否循环滚动
* 设置后调用{@link #reset(boolean)} 来重新初始化布局
*
* @param enableLoop
*/
public void setEnableLoop(boolean enableLoop) {
mEnableLoop = enableLoop;
}
/**
* 是否允许旋转
*
* @return
*/
public boolean isEnableRotation() {
return mEnableRotation;
}
/**
* 是否循环滚动
*
* @return
*/
public boolean isEnableLoop() {
return mEnableLoop;
}
public int getCurrIndex() {
//sync?
return mIndex;
}
//only necessary when I need the attrs from xml, this will be used when inflating layout
public CardStack(Context context, AttributeSet attrs) {
super(context, attrs);
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CardStack);
mColor = array.getColor(R.styleable.CardStack_card_backgroundColor, mColor);
mGravity = array.getInteger(R.styleable.CardStack_card_gravity, Gravity.BOTTOM);
mEnableRotation = array.getBoolean(R.styleable.CardStack_card_enable_rotation, false);
mNumVisible = array.getInteger(R.styleable.CardStack_card_stack_size, mNumVisible);
mEnableLoop = array.getBoolean(R.styleable.CardStack_card_enable_loop, mEnableLoop);
mMargin = array.getDimensionPixelOffset(R.styleable.CardStack_card_margin, 20);
array.recycle();
}
//get attrs assign minVisiableNum
for (int i = 0; i < mNumVisible; i++) {
addContainerViews(false);
}
setupAnimation();
}
private void addContainerViews(boolean anim) {
FrameLayout v = new FrameLayout(getContext());
viewCollection.add(v);
addView(v);
if (anim) {
Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.undo_anim);
v.startAnimation(animation);
}
}
public void setStackMargin(int margin) {
mMargin = margin;
mCardAnimator.setStackMargin(mMargin);
mCardAnimator.initLayout();
}
public int getStackMargin() {
return mMargin;
}
public void setContentResource(int res) {
mContentResource = res;
}
public void setCanSwipe(boolean can) {
this.canSwipe = can;
}
public void reset(boolean resetIndex) {
reset(resetIndex, false);
}
private void reset(boolean resetIndex, boolean animFirst) {
if (resetIndex) mIndex = 0;
removeAllViews();
viewCollection.clear();
for (int i = 0; i < mNumVisible; i++) {
addContainerViews(i == mNumVisible - 1 && animFirst);
}
setupAnimation();
loadData();
}
public void setVisibleCardNum(int visiableNum) {
mNumVisible = visiableNum;
if (mNumVisible >= mAdapter.getCount()) {
mNumVisible = mAdapter.getCount();
}
reset(false);
}
public void setThreshold(int t) {
mEventListener = new DefaultStackEventListener(t);
}
public void setListener(CardEventListener cel) {
mEventListener = cel;
}
private void setupAnimation() {
final View cardView = viewCollection.get(viewCollection.size() - 1);
mCardAnimator = new CardAnimator(viewCollection, mColor, mMargin);
mCardAnimator.setGravity(mGravity);
mCardAnimator.setEnableRotation(mEnableRotation);
//mCardAnimator.setStackMargin(mMargin);
mCardAnimator.initLayout();
final DragGestureDetector dd = new DragGestureDetector(CardStack.this.getContext(), new DragGestureDetector.DragListener() {
@Override
public boolean onDragStart(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
if (canSwipe) {
mCardAnimator.drag(e1, e2, distanceX, distanceY);
}
float x1 = e1.getRawX();
float y1 = e1.getRawY();
float x2 = e2.getRawX();
float y2 = e2.getRawY();
final int direction = CardUtils.direction(x1, y1, x2, y2);
float distance = CardUtils.distance(x1, y1, x2, y2);
mEventListener.swipeStart(direction, distance);
return true;
}
@Override
public boolean onDragContinue(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
float x1 = e1.getRawX();
float y1 = e1.getRawY();
float x2 = e2.getRawX();
float y2 = e2.getRawY();
final int direction = CardUtils.direction(x1, y1, x2, y2);
if (canSwipe) {
mCardAnimator.drag(e1, e2, distanceX, distanceY);
}
mEventListener.swipeContinue(direction, Math.abs(x2 - x1), Math.abs(y2 - y1));
return true;
}
@Override
public boolean onDragEnd(MotionEvent e1, MotionEvent e2) {
//reverse(e1,e2);
float x1 = e1.getRawX();
float y1 = e1.getRawY();
float x2 = e2.getRawX();
float y2 = e2.getRawY();
float distance = CardUtils.distance(x1, y1, x2, y2);
final int direction = CardUtils.direction(x1, y1, x2, y2);
boolean discard = mEventListener.swipeEnd(direction, distance);
if (discard) {
if (canSwipe) {
mCardAnimator.discard(direction, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator arg0) {
mCardAnimator.initLayout();
mIndex++;
mEventListener.discarded(mIndex, direction);
//mIndex = mIndex%mAdapter.getCount();
loadLast();
viewCollection.get(0).setOnTouchListener(null);
viewCollection.get(viewCollection.size() - 1)
.setOnTouchListener(mOnTouchListener);
}
});
}
} else {
if (canSwipe) {
mCardAnimator.reverse(e1, e2);
}
}
return true;
}
@Override
public boolean onTapUp() {
mEventListener.topCardTapped();
return true;
}
}
);
mOnTouchListener = new OnTouchListener() {
private static final String DEBUG_TAG = "MotionEvents";
@Override
public boolean onTouch(View arg0, MotionEvent event) {
dd.onTouchEvent(event);
return true;
}
};
cardView.setOnTouchListener(mOnTouchListener);
}
private DataSetObserver mOb = new DataSetObserver() {
@Override
public void onChanged() {
reset(false);
}
};
//ArrayList
ArrayList<View> viewCollection = new ArrayList<View>();
public CardStack(Context context) {
super(context);
}
public void setAdapter(final ArrayAdapter<?> adapter) {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mOb);
}
mAdapter = adapter;
adapter.registerDataSetObserver(mOb);
loadData();
}
public ArrayAdapter getAdapter() {
return mAdapter;
}
public View getTopView() {
return ((ViewGroup) viewCollection.get(viewCollection.size() - 1)).getChildAt(0);
}
private void loadData() {
for (int i = mNumVisible - 1; i >= 0; i--) {
ViewGroup parent = (ViewGroup) viewCollection.get(i);
int index = (mIndex + mNumVisible - 1) - i;
if (index > mAdapter.getCount() - 1) {
parent.setVisibility(View.GONE);
} else {
View child = mAdapter.getView(index, getContentView(), this);
parent.addView(child);
parent.setVisibility(View.VISIBLE);
}
}
}
private View getContentView() {
View contentView = null;
if (mContentResource != 0) {
LayoutInflater lf = LayoutInflater.from(getContext());
contentView = lf.inflate(mContentResource, null);
}
return contentView;
}
// 加载下一个
private void loadLast() {
ViewGroup parent = (ViewGroup) viewCollection.get(0);
int lastIndex = ((mNumVisible - 1) + mIndex);
// 超出索引
if (lastIndex > mAdapter.getCount() - 1) {
if (mEnableLoop) {
// 循环处理
lastIndex = lastIndex % mAdapter.getCount();
} else {
parent.setVisibility(View.GONE);
return;
}
}
View child = mAdapter.getView(lastIndex, getContentView(), parent);
parent.removeAllViews();
parent.addView(child);
}
/**
* 获取可见卡片个数
*
* @return
*/
public int getVisibleCardNum() {
return mNumVisible;
}
public void undo() {
if (mIndex == 0) return;
mIndex --;
reset(false, true);
}
}