一款Android开源的下拉刷新动画

版权所有,禁止匿名转载;禁止商业使用。
package com.example.pullrefersh;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.ImageView;
import java.security.InvalidParameterException;
import refresh_view.BaseRefreshView;
import refresh_view.SunRefreshView;
import util.Utils;
public class PullToRefreshView extends ViewGroup {
  private static final int DRAG_MAX_DISTANCE = 120;
  private static final float DRAG_RATE = .5f;
  private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
  public static final int STYLE_SUN = 0;
  public static final int STYLE_JET = 1;
  public static final int MAX_OFFSET_ANIMATION_DURATION = 700;
  private static final int INVALID_POINTER = -1;
  private View mTarget;
  private ImageView mRefreshView;
  private Interpolator mDecelerateInterpolator;
  private int mTouchSlop;
  private int mTotalDragDistance;
  private BaseRefreshView mBaseRefreshView;
  private float mCurrentDragPercent;
  private int mCurrentOffsetTop;
  private boolean mRefreshing;
  private int mActivePointerId;
  private boolean mIsBeingDragged;
  private float mInitialMotionY;
  private int mFrom;
  private float mFromDragPercent;
  private boolean mNotify;
  private OnRefreshListener mListener;
  public PullToRefreshView(Context context) {
    this(context, null);
  }
  public PullToRefreshView(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RefreshView);
    final int type = a.getInteger(R.styleable.RefreshView_type, STYLE_SUN);
    a.recycle();
    mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
    mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    mTotalDragDistance = Utils.convertDpToPixel(context, DRAG_MAX_DISTANCE);
    mRefreshView = new ImageView(context);
    setRefreshStyle(type);
    addView(mRefreshView);
    setWillNotDraw(false);
//ViewCompat.setChildrenDrawingOrderEnabled(this, true);
  }
  public void setRefreshStyle(int type) {
    setRefreshing(false);
    switch (type) {
      case STYLE_SUN:
        mBaseRefreshView = new SunRefreshView(getContext(), this);
        break;
      case STYLE_JET:
        // TODO
      default:
        throw new InvalidParameterException("Type does not exist");
    }
    mRefreshView.setImageDrawable(mBaseRefreshView);
  }
  public int getTotalDragDistance() {
    return mTotalDragDistance;
  }
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    ensureTarget();
    if (mTarget == null)
      return;
    widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingRight() - getPaddingLeft(), MeasureSpec.EXACTLY);
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
    mTarget.measure(widthMeasureSpec, heightMeasureSpec);
    mRefreshView.measure(widthMeasureSpec, heightMeasureSpec);
  }
  private void ensureTarget() {
    if (mTarget != null)
      return;
    if (getChildCount() > 0) {
      for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        if (child != mRefreshView)
          mTarget = child;
      }
    }
  }
  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (!isEnabled() || canChildScrollUp() || mRefreshing) {
      return false;
    }
    final int action = MotionEventCompat.getActionMasked(ev);
    switch (action) {
      case MotionEvent.ACTION_DOWN:
        setTargetOffsetTop(0, true);
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        mIsBeingDragged = false;
        final float initialMotionY = getMotionEventY(ev, mActivePointerId);
        if (initialMotionY == -1) {
          return false;
        }
        mInitialMotionY = initialMotionY;
        break;
      case MotionEvent.ACTION_MOVE:
        if (mActivePointerId == INVALID_POINTER) {
          return false;
        }
        final float y = getMotionEventY(ev, mActivePointerId);
        if (y == -1) {
          return false;
        }
        final float yDiff = y - mInitialMotionY;
        if (yDiff > mTouchSlop && !mIsBeingDragged) {
          mIsBeingDragged = true;
        }
        break;
      case MotionEvent.ACTION_UP:
      case MotionEvent.ACTION_CANCEL:
        mIsBeingDragged = false;
        mActivePointerId = INVALID_POINTER;
        break;
      case MotionEventCompat.ACTION_POINTER_UP:
        onSecondaryPointerUp(ev);
        break;
    }
    return mIsBeingDragged;
  }
  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    if (!mIsBeingDragged) {
      return super.onTouchEvent(ev);
    }
    final int action = MotionEventCompat.getActionMasked(ev);
    switch (action) {
      case MotionEvent.ACTION_MOVE: {
        final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
        if (pointerIndex < 0) {
          return false;
        }
        final float y = MotionEventCompat.getY(ev, pointerIndex);
        final float yDiff = y - mInitialMotionY;
        final float scrollTop = yDiff * DRAG_RATE;
        mCurrentDragPercent = scrollTop / mTotalDragDistance;
        if (mCurrentDragPercent < 0) {
          return false;
        }
        float boundedDragPercent = Math.min(1f, Math.abs(mCurrentDragPercent));
        float extraOS = Math.abs(scrollTop) - mTotalDragDistance;
        float slingshotDist = mTotalDragDistance;
        float tensionSlingshotPercent = Math.max(0,
            Math.min(extraOS, slingshotDist * 2) / slingshotDist);
        float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
            (tensionSlingshotPercent / 4), 2)) * 2f;
        float extraMove = (slingshotDist) * tensionPercent / 2;
        int targetY = (int) ((slingshotDist * boundedDragPercent) + extraMove);
        mBaseRefreshView.setPercent(mCurrentDragPercent, true);
        setTargetOffsetTop(targetY - mCurrentOffsetTop, true);
        break;
      }
      case MotionEventCompat.ACTION_POINTER_DOWN:
        final int index = MotionEventCompat.getActionIndex(ev);
        mActivePointerId = MotionEventCompat.getPointerId(ev, index);
        break;
      case MotionEventCompat.ACTION_POINTER_UP:
        onSecondaryPointerUp(ev);
        break;
      case MotionEvent.ACTION_UP:
      case MotionEvent.ACTION_CANCEL: {
        if (mActivePointerId == INVALID_POINTER) {
          return false;
        }
        final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
        final float y = MotionEventCompat.getY(ev, pointerIndex);
        final float overScrollTop = (y - mInitialMotionY) * DRAG_RATE;
        mIsBeingDragged = false;
        if (overScrollTop > mTotalDragDistance) {
          setRefreshing(true, true);
        } else {
          mRefreshing = false;
          animateOffsetToStartPosition();
        }
        mActivePointerId = INVALID_POINTER;
        return false;
      }
    }
    return true;
  }
  private void animateOffsetToStartPosition() {
    mFrom = mCurrentOffsetTop;
    mFromDragPercent = mCurrentDragPercent;
    long animationDuration = Math.abs((long) (MAX_OFFSET_ANIMATION_DURATION * mFromDragPercent));
    mAnimateToStartPosition.reset();
    mAnimateToStartPosition.setDuration(animationDuration);
    mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
    mAnimateToStartPosition.setAnimationListener(mToStartListener);
    mRefreshView.clearAnimation();
    mRefreshView.startAnimation(mAnimateToStartPosition);
  }
  private void animateOffsetToCorrectPosition() {
    mFrom = mCurrentOffsetTop;
    mFromDragPercent = mCurrentDragPercent;
    mAnimateToCorrectPosition.reset();
    mAnimateToCorrectPosition.setDuration(MAX_OFFSET_ANIMATION_DURATION);
    mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
    mRefreshView.clearAnimation();
    mRefreshView.startAnimation(mAnimateToCorrectPosition);
    if (mRefreshing) {
      mBaseRefreshView.start();
      if (mNotify) {
        if (mListener != null) {
          mListener.onRefresh();
        }
      }
    } else {
      mBaseRefreshView.stop();
      animateOffsetToStartPosition();
    }
    mCurrentOffsetTop = mTarget.getTop();
  }
  private final Animation mAnimateToStartPosition = new Animation() {
    @Override
    public void applyTransformation(float interpolatedTime, Transformation t) {
      moveToStart(interpolatedTime);
    }
  };
  private final Animation mAnimateToCorrectPosition = new Animation() {
    @Override
    public void applyTransformation(float interpolatedTime, Transformation t) {
      int targetTop;
      int endTarget = mTotalDragDistance;
      targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
      int offset = targetTop - mTarget.getTop();
      mCurrentDragPercent = mFromDragPercent - (mFromDragPercent - 1.0f) * interpolatedTime;
      mBaseRefreshView.setPercent(mCurrentDragPercent, false);
      setTargetOffsetTop(offset, false /* requires update */);
    }
  };
  private void moveToStart(float interpolatedTime) {
    int targetTop = mFrom - (int) (mFrom * interpolatedTime);
    float targetPercent = mFromDragPercent * (1.0f - interpolatedTime);
    int offset = targetTop - mTarget.getTop();
    mCurrentDragPercent = targetPercent;
    mBaseRefreshView.setPercent(mCurrentDragPercent, true);
    setTargetOffsetTop(offset, false);
  }
  public void setRefreshing(boolean refreshing) {
    if (mRefreshing != refreshing) {
      setRefreshing(refreshing, false /* notify */);
    }
  }
  private void setRefreshing(boolean refreshing, final boolean notify) {
    if (mRefreshing != refreshing) {
      mNotify = notify;
      ensureTarget();
      mRefreshing = refreshing;
      if (mRefreshing) {
        mBaseRefreshView.setPercent(1f, true);
        animateOffsetToCorrectPosition();
      } else {
        animateOffsetToStartPosition();
      }
    }
  }
  private Animation.AnimationListener mToStartListener = new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
    }
    @Override
    public void onAnimationRepeat(Animation animation) {
    }
    @Override
    public void onAnimationEnd(Animation animation) {
      mBaseRefreshView.stop();
      mCurrentOffsetTop = mTarget.getTop();
    }
  };
  private void onSecondaryPointerUp(MotionEvent ev) {
    final int pointerIndex = MotionEventCompat.getActionIndex(ev);
    final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
    if (pointerId == mActivePointerId) {
      final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
      mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
    }
  }
  private float getMotionEventY(MotionEvent ev, int activePointerId) {
    final int index = MotionEventCompat.findPointerIndex(ev, activePointerId);
    if (index < 0) {
      return -1;
    }
    return MotionEventCompat.getY(ev, index);
  }
  private void setTargetOffsetTop(int offset, boolean requiresUpdate) {
    mTarget.offsetTopAndBottom(offset);
    mBaseRefreshView.offsetTopAndBottom(offset);
    mCurrentOffsetTop = mTarget.getTop();
    if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) {
      invalidate();
    }
  }
  private boolean canChildScrollUp() {
    if (android.os.Build.VERSION.SDK_INT < 14) {
      if (mTarget instanceof AbsListView) {
        final AbsListView absListView = (AbsListView) mTarget;
        return absListView.getChildCount() > 0
            && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
            .getTop() < absListView.getPaddingTop());
      } else {
        return mTarget.getScrollY() > 0;
      }
    } else {
      return ViewCompat.canScrollVertically(mTarget, -1);
    }
  }
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    ensureTarget();
    if (mTarget == null)
      return;
    int height = getMeasuredHeight();
    int width = getMeasuredWidth();
    int left = getPaddingLeft();
    int top = getPaddingTop();
    int right = getPaddingRight();
    int bottom = getPaddingBottom();
    mTarget.layout(left, top + mCurrentOffsetTop, left + width - right, top + height - bottom + mCurrentOffsetTop);
    mRefreshView.layout(left, top, left + width - right, top + height - bottom);
  }
  public void setOnRefreshListener(OnRefreshListener listener) {
    mListener = listener;
  }
  public static interface OnRefreshListener {
    public void onRefresh();
  }
}


0 0