package com.zcy.ghost.vivideo.widget;
/**
* Description:
* Creator: yxc
* date: $date $time
*/
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.os.AsyncTask;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.FrameLayout;
import com.daprlabs.cardstack.SwipeListener;
import java.util.ArrayList;
/**
* Created by aaron on 4/12/2015.
*/
public class SwipeDeck extends FrameLayout {
private static final String TAG = "SwipeDeck.java";
private static int NUMBER_OF_CARDS;
private float ROTATION_DEGREES;
private float CARD_SPACING;
private boolean RENDER_ABOVE;
private boolean RENDER_BELOW;
private float OPACITY_END;
private int CARD_GRAVITY;
private int paddingLeft;
private boolean hardwareAccelerationEnabled = true;
private int paddingRight;
private int paddingTop;
private int paddingBottom;
private SwipeEventCallback eventCallback;
private com.daprlabs.cardstack.SwipeDeck.CardPositionCallback cardPosCallback;
/**
* The adapter with all the data
*/
private Adapter mAdapter;
DataSetObserver observer;
int nextAdapterCard = 0;
private boolean restoreInstanceState = false;
private SwipeListener swipeListener;
private int leftImageResource;
private int rightImageResource;
private boolean cardInteraction;
public SwipeDeck(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
com.daprlabs.cardstack.R.styleable.SwipeDeck,
0, 0);
try {
NUMBER_OF_CARDS = a.getInt(com.daprlabs.cardstack.R.styleable.SwipeDeck_max_visible, 3);
ROTATION_DEGREES = a.getFloat(com.daprlabs.cardstack.R.styleable.SwipeDeck_rotation_degrees, 15f);
CARD_SPACING = a.getDimension(com.daprlabs.cardstack.R.styleable.SwipeDeck_card_spacing, 15f);
RENDER_ABOVE = a.getBoolean(com.daprlabs.cardstack.R.styleable.SwipeDeck_render_above, true);
RENDER_BELOW = a.getBoolean(com.daprlabs.cardstack.R.styleable.SwipeDeck_render_below, false);
CARD_GRAVITY = a.getInt(com.daprlabs.cardstack.R.styleable.SwipeDeck_card_gravity, 0);
OPACITY_END = a.getFloat(com.daprlabs.cardstack.R.styleable.SwipeDeck_opacity_end, 0.33f);
} finally {
a.recycle();
}
paddingBottom = getPaddingBottom();
paddingLeft = getPaddingLeft();
paddingRight = getPaddingRight();
paddingTop = getPaddingTop();
//set clipping of view parent to false so cards render outside their view boundary
//make sure not to clip to padding
setClipToPadding(false);
setClipChildren(false);
this.setWillNotDraw(false);
//render the cards and card deck above or below everything
if (RENDER_ABOVE) {
ViewCompat.setTranslationZ(this, Float.MAX_VALUE);
}
if (RENDER_BELOW) {
ViewCompat.setTranslationZ(this, Float.MIN_VALUE);
}
}
/**
* Set Hardware Acceleration Enabled.
*
* @param accel
*/
public void setHardwareAccelerationEnabled(Boolean accel) {
this.hardwareAccelerationEnabled = accel;
}
public void setAdapter(Adapter adapter) {
if (this.mAdapter != null) {
this.mAdapter.unregisterDataSetObserver(observer);
}
mAdapter = adapter;
// if we're not restoring previous instance state
if (!restoreInstanceState) nextAdapterCard = 0;
observer = new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
//handle data set changes
//if we need to add any cards at this point (ie. the amount of cards on screen
//is less than the max number of cards to display) add the cards.
int childCount = getChildCount();
//only perform action if there are less cards on screen than NUMBER_OF_CARDS
if (childCount < NUMBER_OF_CARDS) {
for (int i = childCount; i < NUMBER_OF_CARDS; ++i) {
addNextCard();
}
//position the items correctly on screen
for (int i = 0; i < getChildCount(); ++i) {
positionItem(i);
}
}
}
@Override
public void onInvalidated() {
//reset state, remove views and request layout
nextAdapterCard = 0;
removeAllViews();
requestLayout();
}
};
adapter.registerDataSetObserver(observer);
removeAllViewsInLayout();
requestLayout();
}
public void setSelection(int position) {
if (position < mAdapter.getCount()) {
this.nextAdapterCard = position;
removeAllViews();
requestLayout();
}
}
public View getSelectedView() {
throw new UnsupportedOperationException("Not supported");
}
@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 || mAdapter.getCount() == 0) {
nextAdapterCard = 0;
removeAllViewsInLayout();
return;
}
//pull in views from the adapter at the position the top of the deck is set to
//stop when you get to for cards or the end of the adapter
int childCount = getChildCount();
for (int i = childCount; i < NUMBER_OF_CARDS; ++i) {
addNextCard();
}
for (int i = 0; i < getChildCount(); ++i) {
positionItem(i);
}
//position the new children we just added and set up the top card with a listener etc
}
private void removeTopCard() {
//top card is now the last in view children
final View child = getChildAt(getChildCount() - 1);
if (child != null) {
child.setOnTouchListener(null);
swipeListener = null;
//this will also check to see if cards are depleted
removeViewWaitForAnimation(child);
}
}
private void removeViewWaitForAnimation(View child) {
new RemoveViewOnAnimCompleted().execute(child);
}
@Override
public void removeView(View view) {
super.removeView(view);
}
private void addNextCard() {
if (nextAdapterCard < mAdapter.getCount()) {
// TODO: Make view recycling work
// TODO: Instead of removing the view from here and adding it again when it's swiped
// ... don't remove and add to this instance: don't call removeView & addView in sequence.
View newBottomChild = mAdapter.getView(nextAdapterCard, null/*lastRemovedView*/, this);
if (hardwareAccelerationEnabled) {
//set backed by an off-screen buffer
newBottomChild.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
//set the initial Y value so card appears from under the deck
//newBottomChild.setY(paddingTop);
addAndMeasureChild(newBottomChild);
nextAdapterCard++;
}
setupTopCard();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setZTranslations() {
//this is only needed to add shadows to cardviews on > lollipop
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int count = getChildCount();
for (int i = 0; i < count; ++i) {
getChildAt(i).setTranslationZ(i * 10);
}
}
}
/**
* Adds a view as a child view and takes care of measuring it
*
* @param child The view to add
*/
private void addAndMeasureChild(View child) {
ViewGroup.LayoutParams params = child.getLayoutParams();
if (params == null) {
params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
//ensure new card is under the deck at the beginning
child.setY(paddingTop);
//every time we add and measure a child refresh the children on screen and order them
ArrayList<View> children = new ArrayList<>();
children.add(child);
for (int i = 0; i < getChildCount(); ++i) {
children.add(getChildAt(i));
}
removeAllViews();
for (View c : children) {
addViewInLayout(c, -1, params, true);
int itemWidth = getWidth() - (paddingLeft + paddingRight);
int itemHeight = getHeight() - (paddingTop + paddingBottom);
c.measure(MeasureSpec.EXACTLY | itemWidth, MeasureSpec.EXACTLY | itemHeight); //MeasureSpec.UNSPECIFIED
//ensure that if there's a left and right image set their alpha to 0 initially
//alpha animation is handled in the swipe listener
if (leftImageResource != 0) child.findViewById(leftImageResource).setAlpha(0);
if (rightImageResource != 0) child.findViewById(rightImageResource).setAlpha(0);
}
setZTranslations();
}
/**
* Positions the children at the "correct" positions
*/
private void positionItem(int index) {
View child = getChildAt(index);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
int left = (getWidth() - width) / 2;
child.layout(left, paddingTop, left + width, paddingTop + height);
//layout each child slightly above the previous child (we start with the bottom)
int childCount = getChildCount();
float offset = (int) (((childCount - 1) * CARD_SPACING) - (index * CARD_SPACING));
//child.setY(paddingTop + offset);
child.animate()
.setDuration(restoreInstanceState ? 0 : 160)
.y(paddingTop + offset);
restoreInstanceState = false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = widthSize;
} else {
//Be whatever you want
width = widthSize;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = heightSize;
} else {
//Be whatever you want
height = heightSize;
}
setMeasuredDimension(width, height);
}
private void setupTopCard() {
int childCount = getChildCount();
final View child;
//TODO: maybe find a better solution this is kind of hacky
//if there's an extra card on screen that means the top card is still being animated
//in that case setup the next card along
if (childCount == (NUMBER_OF_CARDS + 1)) {
child = getChildAt(getChildCount() - 2);
} else {
child = getChildAt(getChildCount() - 1);
}
//this calculation is to get the correct position in the adapter of the current top card
//the card position on setup top card is currently always the bottom card in the view
//at any given time.
int initialX = paddingLeft;
int initialY = paddingTop;
if (child != null) {
//make sure we have a card
swipeListener = new SwipeListener(child, new SwipeListener.SwipeCallback() {
@Override
public void cardSwipedLeft() {
int positionInAdapter = nextAdapterCard - getChildCount();
removeTopCard();
if (eventCallback != null) eventCallback.cardSwipedLeft(positionInAdapter);
addNextCard();
}
@Override
public void cardSwipedRight() {
int positionInAdapter = nextAdapterCard - getChildCount();
removeTopCard();
if (eventCallback != null) eventCallback.cardSwipedRight(positionInAdapter);
addNextCard();
}
@Override
public void cardOffScreen() {
}
@Override
public void cardActionDown() {
if (eventCallback != null) eventCallback.cardActionDown();
cardInteraction = true;
}
@Override
public void cardActionUp() {
if (eventCallback != null) eventCallback.cardActionUp();
cardInteraction = false;
}
}, initialX, initialY, ROTATION_DEGREES, OPACITY_END);
//if we specified these image resources, get the views and pass them to the swipe listener
//for the sake of animating them
View rightView = null;
View leftView = null;
if (!(rightImageResource == 0)) rightView = child.findViewById(rightImageResource);
if (!(leftImageResource == 0)) leftView = child.findViewById(leftImageResource);
swipeListener.setLeftView(leftView);
swipeListener.setRightView(rightView);
child.setOnTouchListener(swipeListener);
}
}
public void setEventCallback(SwipeEventCallback eventCallback) {
this.eventCallback = eventCallback;
}
public void swipeTopCardLeft(int duration) {
int childCount = getChildCount();
if (childCount > 0 && getChildCount() < (NUMBER_OF_CARDS + 1)) {
swipeListener.animateOffScreenLeft(duration);
int positionInAdapter = nextAdapterCard - getChildCount();
removeTopCard();
if (eventCallback != null) eventCallback.cardSwipedLeft(positionInAdapter);
addNextCard();
}
}
public void swipeTopCardRight(int duration) {
int childCount = getChildCount();
if (childCount > 0 && getChildCount() < (NUMBER_OF_CARDS + 1)) {
swipeListener.animateOffScreenRight(duration);
int positionInAdapter = nextAdapterCard - getChildCount();
removeTopCard();
if (eventCallback != null) eventCallback.cardSwipedRight(positionInAdapter);
addNextCard();
}
}
public void setPositionCallback(com.daprlabs.cardstack.SwipeDeck.CardPositionCallback callback) {
cardPosCallback = callback;
}
public void setLeftImage(int imageResource) {
leftImageResource = imageResource;
}
public void setRightImage(int imageResource) {
rightImageResource = imageResource;
}
public interface SwipeEventCallback {
//returning the object position in the adapter
void cardSwipedLeft(int position);
void cardSwipedRight(int position);
void cardsDepleted();
void cardActionDown();
void cardActionUp();
}
public interface CardPositionCallback {
void xPos(Float x);
void yPos(Float y);
}
private int AnimationTime = 160;
private class RemoveViewOnAnimCompleted extends AsyncTask<View, Void, View> {
@Override
protected View doInBackground(View... params) {
android.os.SystemClock.sleep(AnimationTime);
return params[0];
}
@Override
protected void onPostExecute(View view) {
super.onPostExecute(view);
removeView(view);
//if there are no more children left after top card removal let the callback know
if (getChildCount() <= 0 && eventCallback != null) {
eventCallback.cardsDepleted();
}
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//拦截resideLayout的事件并消费
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
}