package com.marshalchen.common.uimodule.kugouLayout;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.marshalchen.common.uimodule.rebound.Spring;
import com.marshalchen.common.uimodule.rebound.SpringConfig;
import com.marshalchen.common.uimodule.rebound.SpringListener;
import com.marshalchen.common.uimodule.rebound.SpringSystem;
import com.marshalchen.common.uimodule.widgets.R;
import java.util.ArrayList;
import java.util.Iterator;
/**
* Created by zzt on 2015/2/11.
*/
public class KugouLayout extends ViewGroup {
private static final String TAG = "KugouLayout";
private LayoutCloseListener mLayoutCloseListener;
private KugouLayout mKugouLayout;
private UnClickableFrameLayout mContentContainer;
private AnimatorSet mAnimatorSet;
private ObjectAnimator mOffsetAnimator;
private Interpolator mInterpolator = new DecelerateInterpolator();
private SpringSystem mSpringSystem;
private Spring mSpring;
private ArrayList<View> scrollChildList = new ArrayList<>();
private VelocityTracker mVelocityTracker;
private Activity mParentActivity;
private int mDragRange;
private int mCloseDistance;
private int mWidth;
protected float mLastMotionX = -1;
protected float mLastMotionY = -1;
protected float mInitialMotionX;
protected float mInitialMotionY;
protected float mOffsetPixels;
private boolean mBackgroundVisible;
private boolean mIsDragging = false;
private boolean doAnim = false;
private boolean closingChangeAlpha = false;
private boolean showingChangeAlpha = false;
protected static final int INVALID_POINTER = -1;
protected static final int STATE_CLOSED = 0;
protected static final int STATE_CLOSING = 1;
protected static final int STATE_DRAGGING = 2;
protected static final int STATE_OPENING = 4;
protected static final int STATE_OPEN = 8;
protected static final int LEFT = 0;
protected static final int RIGHT = 1;
public static final int NORMAL_ANIM = 0;
public static final int REBOUND_ANIM = 1;
public static final int ALWAYS_REBOUND = 2;
private int mAnimType = 0;
protected int mLayoutState = STATE_CLOSED;
protected int mActivePointerId = INVALID_POINTER;
protected int mTouchSlop;
protected int mMaxVelocity;
private float mBeginOffsetX;
private int ANIM_DURATION = 300;
public static final boolean USE_TRANSLATIONS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
public KugouLayout(Context context) {
this(context, null);
}
public KugouLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public KugouLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
mKugouLayout = this;
if(context instanceof Activity)
mParentActivity = (Activity)context;
}
private void init(Context context){
setBackgroundColor(0x0);
/**
* Distance in pixels a touch can wander before we think the user is scrolling
* */
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
mMaxVelocity = configuration.getScaledMaximumFlingVelocity();
mContentContainer = new UnClickableFrameLayout(context);
mContentContainer.setId(R.id.kugou_layout_md__content);
/**
* init Property Animation
* */
mAnimatorSet = new AnimatorSet();
mOffsetAnimator = ObjectAnimator.ofFloat(this, aOffset, 0, 0);
mOffsetAnimator.setDuration(ANIM_DURATION);
mAnimatorSet.playTogether(mOffsetAnimator);
mAnimatorSet.setInterpolator(mInterpolator);
mOffsetAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
float endValue = (Float)mOffsetAnimator.getAnimatedValue();
if((endValue == getWidth() || endValue == -getWidth())){
setVisibility(INVISIBLE);
if(null != mLayoutCloseListener) {
mLayoutCloseListener.onLayoutClose();
}
}else if(endValue == 0 && showingChangeAlpha) {
showingChangeAlpha = false;
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
/**
* create rebound animator
* */
mSpringSystem = SpringSystem.create();
mSpring = mSpringSystem.createSpring();
SpringConfig config = new SpringConfig(70, 9);
mSpring.setSpringConfig(config);
mSpring.setCurrentValue(0);
mSpring.addListener(new SpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
if(doAnim) {
double newValue = 1 - spring.getCurrentValue();
mOffsetPixels = (float) newValue * mBeginOffsetX;
moveContent();
if(showingChangeAlpha){
changeAlpha();
}
}
}
@Override
public void onSpringAtRest(Spring spring) {
}
@Override
public void onSpringActivate(Spring spring) {
}
@Override
public void onSpringEndStateChange(Spring spring) {
}
});
/**
* add frame_layout mContentContainer as the parent of the activity's content view
* */
super.addView(mContentContainer, 0, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mContentContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
private void attachToContent(Activity activity, KugouLayout kugouLayout){
Window window = activity.getWindow();
ViewGroup decorView = (ViewGroup)window.getDecorView();
ViewGroup decorChild= (ViewGroup)decorView.getChildAt(0);
decorView.removeAllViews();
mKugouLayout.mContentContainer.addView(decorChild, decorChild.getLayoutParams());
decorView.addView(mKugouLayout, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
window.setBackgroundDrawable(new ColorDrawable(0x0));
}
public KugouLayout attach(Activity activity){
mKugouLayout.setId(R.id.kugou_layout_md__drawer);
attachToContent(activity, mKugouLayout);
mKugouLayout.mContentContainer.setBackgroundColor(0x0);
return mKugouLayout;
}
public void setContentView(View view){
if(mKugouLayout.mContentContainer.getChildCount()!=0){
throw new RuntimeException("kugou layout can only have one direct child view ");
}
mKugouLayout.mContentContainer.addView(view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
public void setContentView(int ResId){
setContentView(mParentActivity.getLayoutInflater().inflate(ResId, null));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**
* get Screen Size
* */
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height= MeasureSpec.getSize(heightMeasureSpec);
mWidth = width;
/**
* set the Size of mContentContainer
* */
final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
final int contentHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, height);
mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec);
/**
* set mDragRange FULLSCREEN
* */
mDragRange = getChildMeasureSpec(widthMeasureSpec, 0, width);
mCloseDistance= mDragRange/2;
/**
* onMeasure() must set the measured dimension by calling setMeasuredDimension()
* */
setMeasuredDimension(width, height);
updateDragRange();
updatePivot();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height= b - t;
if(USE_TRANSLATIONS){
mContentContainer.layout(0, 0, width, height);
}else{
final int offsetPixels = (int)mOffsetPixels;
mContentContainer.layout(offsetPixels, 0, width+offsetPixels, height);
}
}
private void updateDragRange(){
mDragRange = getMeasuredWidth();
}
private void updatePivot(){
mContentContainer.setPivotX(getWidth() / 2);
mContentContainer.setPivotY((int)(getHeight()*1.5));
}
private boolean onDownAllowDrag(int x, int y){
return (!mBackgroundVisible && mInitialMotionX <= mDragRange)
||(mBackgroundVisible && mInitialMotionX >= mOffsetPixels);
}
protected boolean onMoveAllowDrag(int x, int y, float dx, float dy){
return (!mBackgroundVisible && mInitialMotionX <= mDragRange)
||(mBackgroundVisible && x>= mOffsetPixels);
}
protected void onMoveEvent(float dx, float dy){
mOffsetPixels += dx;
moveContent();
}
private void moveContent(){
if (USE_TRANSLATIONS) {
mContentContainer.setTranslationX(mOffsetPixels / 2);
mContentContainer.setRotation(mOffsetPixels / 60);
} else {
mContentContainer.offsetLeftAndRight(((int)mOffsetPixels - getLeft())/2);
mContentContainer.setRotation(((int) mOffsetPixels - getLeft())/60);
}
invalidate();
}
private void normalCloseAnimStart(int closeDirection){
closingChangeAlpha = true;
showingChangeAlpha = false;
if(closeDirection == RIGHT)
mOffsetAnimator.setFloatValues(mOffsetPixels, getWidth());
else
mOffsetAnimator.setFloatValues(mOffsetPixels, -getWidth());
mAnimatorSet.start();
}
private void normalAnimStart(){
mOffsetAnimator.setFloatValues(mOffsetPixels, 0);
mAnimatorSet.start();
}
private void normalAnimStop(){
if(mAnimatorSet.isRunning())
mAnimatorSet.cancel();
}
private void reboundAnimStart(){
showingChangeAlpha = false;
mBeginOffsetX = mOffsetPixels;
stopAnim();
mOffsetPixels = mBeginOffsetX;
mSpring.setCurrentValue(0);
mSpring.setEndValue(1);
doAnim = true;
}
private void reboundAnimStop(){
doAnim = false;
double stopValue = mSpring.getCurrentValue();
mSpring.setCurrentValue(stopValue);
}
public void setAnimType(int animType){
switch(animType){
case REBOUND_ANIM:
mAnimType = REBOUND_ANIM;
break;
case NORMAL_ANIM:
mAnimType = NORMAL_ANIM;
break;
case ALWAYS_REBOUND:
mAnimType = ALWAYS_REBOUND;
break;
default:
throw new IllegalArgumentException("animType should be NORMAL_ANIM or NORMAL_ANIM or ALWAYS_REBOUND");
}
}
public void hide(){
setVisibility(INVISIBLE);
}
private void changeAlpha(){
setAlpha(1 - Math.abs(mOffsetPixels)/getWidth());
}
private void normalShowAnim(){
mOffsetAnimator.setFloatValues(getWidth(), 0);
mAnimatorSet.start();
}
private void reboundShowAnim(){
doAnim = true;
mOffsetPixels = mBeginOffsetX = getWidth();
mSpring.setCurrentValue(0);
mSpring.setEndValue(1);
}
public void show(){
showingChangeAlpha = true;
setAlpha(0);
setVisibility(VISIBLE);
if(mAnimType == NORMAL_ANIM) {
normalShowAnim();
}else{
reboundShowAnim();
}
}
private void closeAnim(int closeDirection){
normalCloseAnimStart(closeDirection);
}
private void scrollBackAnim(){
if(REBOUND_ANIM == mAnimType|| ALWAYS_REBOUND == mAnimType)
reboundAnimStart();
else
normalAnimStart();
}
protected void onActionUp(int x, int y){
if(!mIsDragging) {
scrollBackAnim();
return ;
}
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
closingChangeAlpha = false;
if(initialVelocity == 0 || ALWAYS_REBOUND == mAnimType){
scrollBackAnim();
return ;
}
if(Math.abs(initialVelocity)<100 && Math.abs(mOffsetPixels)<mCloseDistance){
if(mOffsetPixels>0)
closeAnim(RIGHT);
else
closeAnim(LEFT);
return ;
}
if(initialVelocity>0){
if(mOffsetPixels >0) {
closeAnim(RIGHT);
}else if(mOffsetPixels <0) {
scrollBackAnim();
}
}else if(initialVelocity<0){
if(mOffsetPixels >0) {
scrollBackAnim();
}else if(mOffsetPixels <0) {
closeAnim(LEFT);
}
}
}
protected void stopAnim(){
setAlpha(1);
if(REBOUND_ANIM == mAnimType || ALWAYS_REBOUND == mAnimType)
reboundAnimStop();
else
normalAnimStop();
}
protected float getXVelocity(VelocityTracker velocityTracker) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
return velocityTracker.getXVelocity(mActivePointerId);
}
return velocityTracker.getXVelocity();
}
protected boolean checkTouchSlop(float dx, float dy){
return Math.abs(dx)>mTouchSlop && Math.abs(dx)>Math.abs(dy);
}
protected boolean checkHorizonSlop(float dx, float dy){
return Math.abs(dy)>mTouchSlop && Math.abs(dy)>Math.abs(dx);
}
protected void setLayoutState(int newState){
mLayoutState = newState;
}
Rect rect = new Rect();
Iterator<View> scrollChildListIterator;
private boolean canChildScroll(float rawX, float rawY){
int[] location = new int[2];
View childView;
scrollChildListIterator = scrollChildList.iterator();
while(scrollChildListIterator.hasNext()){
childView = scrollChildListIterator.next();
childView.getLocationInWindow(location);
rect.set(childView.getLeft(), location[1], childView.getRight(), location[1]+childView.getHeight());
if(rect.contains((int)rawX, (int)rawY)){
return true;
}
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch(action){
case MotionEvent.ACTION_DOWN: {
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
final boolean allowDrag = onDownAllowDrag((int) mInitialMotionX, (int) mInitialMotionY);
mActivePointerId = ev.getPointerId(0);
if (allowDrag) {
setLayoutState(mBackgroundVisible ? STATE_OPEN : STATE_CLOSED);
mIsDragging = false;
stopAnim();
}
break;
}
case MotionEvent.ACTION_MOVE:
final int activePointerId = mActivePointerId;
if(activePointerId == INVALID_POINTER){
break;
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
if(pointerIndex == -1){
mIsDragging = false;
mActivePointerId = INVALID_POINTER;
return false;
}
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
final float dx= x - mLastMotionX;
final float dy= y - mLastMotionY;
if(checkTouchSlop(dx, dy)){
if( mOffsetPixels == 0 && canChildScroll(ev.getRawX(), ev.getRawY())){
onActionUp((int)ev.getX(), (int)ev.getY());
return false;
}
final boolean allowDrag = onMoveAllowDrag((int)x, (int)y, dx, dy);
if(allowDrag){
setLayoutState(STATE_DRAGGING);
mIsDragging = true;
mLastMotionX = x;
mLastMotionY = y;
}
}else if(checkHorizonSlop(dx, dy)){
/**
* continue the anim
* */
onActionUp((int)ev.getX(), (int)ev.getY());
return false;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mActivePointerId = INVALID_POINTER;
mIsDragging = false;
onActionUp((int)ev.getX(), (int)ev.getY());
return false;
}
return mIsDragging;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
/**
* multi touch support
* */
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (mVelocityTracker == null)
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(ev);
switch(action){
case MotionEvent.ACTION_MOVE:
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
if(pointerIndex == -1){
mIsDragging = false;
mActivePointerId = INVALID_POINTER;
Log.i(TAG, "onTouchEvent MotionEvent.ACTION_MOVE return false");
return false;
}
if(!mIsDragging){
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
final float dx= x - mLastMotionX;
final float dy= y - mLastMotionY;
if(checkTouchSlop(dx, dy)){
if(onMoveAllowDrag((int)x, (int)y, dx, dy)){
setLayoutState(STATE_DRAGGING);
mIsDragging = true;
mLastMotionX = x;
mLastMotionY = y;
}else{
mInitialMotionX = x;
mInitialMotionY = y;
}
}
}
if(mIsDragging){
//startLayoutAnimation();
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
onMoveEvent(x - mLastMotionX, y - mLastMotionY);
mLastMotionX = x;
mLastMotionY = y;
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
int index = ev.findPointerIndex(mActivePointerId);
index = index == -1?0:index;
final int x = (int)ev.getX(index);
final int y = (int)ev.getY(index);
onActionUp(x, y);
mActivePointerId = INVALID_POINTER;
mIsDragging = false;
break;
}
return true;
}
Property<KugouLayout, Float> aOffset = new Property<KugouLayout, Float>(Float.class, "mOffsetPixels"){
@Override
public Float get(KugouLayout object) {
return object.mOffsetPixels;
}
@Override
public void set(KugouLayout object, Float value) {
float tempValue = value;
object.mOffsetPixels = tempValue;
moveContent();
if(showingChangeAlpha){
changeAlpha();
}else if(closingChangeAlpha && tempValue>=mWidth*3/5) {
setAlpha((mWidth - tempValue) / (mWidth * 2 / 5));
}else if(closingChangeAlpha && -tempValue>=mWidth*3/5){
setAlpha((mWidth + tempValue) / (mWidth * 2 / 5));
}
}
};
public void addHorizontalScrollableView(View horizontalScrollableView){
scrollChildList.add(horizontalScrollableView);
}
public void setLayoutCloseListener(LayoutCloseListener layoutCloseListener){
mLayoutCloseListener = layoutCloseListener;
}
public interface LayoutCloseListener{
public void onLayoutClose();
}
}