|
|
@@ -0,0 +1,1453 @@
|
|
|
+package com.xzjmyk.pm.activity.view;
|
|
|
+
|
|
|
+import android.animation.Animator;
|
|
|
+import android.animation.AnimatorListenerAdapter;
|
|
|
+import android.animation.ValueAnimator;
|
|
|
+import android.animation.ValueAnimator.AnimatorUpdateListener;
|
|
|
+import android.annotation.SuppressLint;
|
|
|
+import android.annotation.TargetApi;
|
|
|
+import android.content.Context;
|
|
|
+import android.content.res.TypedArray;
|
|
|
+import android.graphics.Canvas;
|
|
|
+import android.graphics.Color;
|
|
|
+import android.graphics.Paint;
|
|
|
+import android.graphics.RectF;
|
|
|
+import android.os.Build;
|
|
|
+import android.os.Handler;
|
|
|
+import android.support.v4.view.MotionEventCompat;
|
|
|
+import android.support.v4.view.ViewCompat;
|
|
|
+import android.support.v7.widget.LinearLayoutManager;
|
|
|
+import android.support.v7.widget.RecyclerView;
|
|
|
+import android.support.v7.widget.RecyclerView.LayoutManager;
|
|
|
+import android.support.v7.widget.StaggeredGridLayoutManager;
|
|
|
+import android.util.AttributeSet;
|
|
|
+import android.util.DisplayMetrics;
|
|
|
+import android.util.Log;
|
|
|
+import android.view.Display;
|
|
|
+import android.view.MotionEvent;
|
|
|
+import android.view.View;
|
|
|
+import android.view.ViewConfiguration;
|
|
|
+import android.view.ViewGroup;
|
|
|
+import android.view.WindowManager;
|
|
|
+import android.view.animation.Animation;
|
|
|
+import android.view.animation.Animation.AnimationListener;
|
|
|
+import android.view.animation.DecelerateInterpolator;
|
|
|
+import android.view.animation.Transformation;
|
|
|
+import android.widget.AbsListView;
|
|
|
+import android.widget.RelativeLayout;
|
|
|
+import android.widget.ScrollView;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @Author Zheng Haibo
|
|
|
+ * @PersonalWebsite http://www.mobctrl.net
|
|
|
+ * @Description 自定义CustomeSwipeRefreshLayout<br>
|
|
|
+ * 支持下拉刷新和上拉加载更多<br>
|
|
|
+ * 非侵入式,对原来的ListView、RecyclerView没有任何影响,用法和SwipeRefreshLayout类似。<br>
|
|
|
+ * 可自定义头部View的样式,调用setHeaderView方法即可 <br>
|
|
|
+ * 可自定义页尾View的样式,调用setFooterView方法即可 <br>
|
|
|
+ * 支持RecyclerView,ListView,ScrollView,GridView等等。<br>
|
|
|
+ * 被包含的View(RecyclerView,ListView etc.)可跟随手指的滑动而滑动<br>
|
|
|
+ * 默认是跟随手指的滑动而滑动,也可以设置为不跟随:setTargetScrollWithLayout(false) 回调方法更多<br>
|
|
|
+ * 比如:onRefresh() onPullDistance(int distance)和onPullEnable(boolean
|
|
|
+ * enable)<br>
|
|
|
+ * 开发人员可以根据下拉过程中distance的值做一系列动画。 <br>
|
|
|
+ */
|
|
|
+@SuppressLint("ClickableViewAccessibility")
|
|
|
+public class SuperSwipeRefreshLayout extends ViewGroup {
|
|
|
+ private static final String LOG_TAG = "CustomeSwipeRefreshLayout";
|
|
|
+ private static final int HEADER_VIEW_HEIGHT = 50;// HeaderView height (dp)
|
|
|
+
|
|
|
+ private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
|
|
|
+ private static final int INVALID_POINTER = -1;
|
|
|
+ private static final float DRAG_RATE = .5f;
|
|
|
+
|
|
|
+ private static final int SCALE_DOWN_DURATION = 150;
|
|
|
+ private static final int ANIMATE_TO_TRIGGER_DURATION = 200;
|
|
|
+ private static final int ANIMATE_TO_START_DURATION = 200;
|
|
|
+ private static final int DEFAULT_CIRCLE_TARGET = 64;
|
|
|
+
|
|
|
+ // SuperSwipeRefreshLayout内的目标View,比如RecyclerView,ListView,ScrollView,GridView
|
|
|
+ // etc.
|
|
|
+ private View mTarget;
|
|
|
+
|
|
|
+ private OnPullRefreshListener mListener;// 下拉刷新listener
|
|
|
+ private OnPushLoadMoreListener mOnPushLoadMoreListener;// 上拉加载更多
|
|
|
+
|
|
|
+ private boolean mRefreshing = false;
|
|
|
+ private boolean mLoadMore = false;
|
|
|
+ private int mTouchSlop;
|
|
|
+ private float mTotalDragDistance = -1;
|
|
|
+ private int mMediumAnimationDuration;
|
|
|
+ private int mCurrentTargetOffsetTop;
|
|
|
+ private boolean mOriginalOffsetCalculated = false;
|
|
|
+
|
|
|
+ private float mInitialMotionY;
|
|
|
+ private boolean mIsBeingDragged;
|
|
|
+ private int mActivePointerId = INVALID_POINTER;
|
|
|
+ private boolean mScale;
|
|
|
+
|
|
|
+ private boolean mReturningToStart;
|
|
|
+ private final DecelerateInterpolator mDecelerateInterpolator;
|
|
|
+ private static final int[] LAYOUT_ATTRS = new int[]{android.R.attr.enabled};
|
|
|
+
|
|
|
+ private HeadViewContainer mHeadViewContainer;
|
|
|
+ private RelativeLayout mFooterViewContainer;
|
|
|
+ private int mHeaderViewIndex = -1;
|
|
|
+ private int mFooterViewIndex = -1;
|
|
|
+
|
|
|
+ protected int mFrom;
|
|
|
+
|
|
|
+ private float mStartingScale;
|
|
|
+
|
|
|
+ protected int mOriginalOffsetTop;
|
|
|
+
|
|
|
+ private Animation mScaleAnimation;
|
|
|
+
|
|
|
+ private Animation mScaleDownAnimation;
|
|
|
+
|
|
|
+ private Animation mScaleDownToStartAnimation;
|
|
|
+
|
|
|
+ // 最后停顿时的偏移量px,与DEFAULT_CIRCLE_TARGET正比
|
|
|
+ private float mSpinnerFinalOffset;
|
|
|
+
|
|
|
+ private boolean mNotify;
|
|
|
+
|
|
|
+ private int mHeaderViewWidth;// headerView的宽度
|
|
|
+
|
|
|
+ private int mFooterViewWidth;
|
|
|
+
|
|
|
+ private int mHeaderViewHeight;
|
|
|
+
|
|
|
+ private int mFooterViewHeight;
|
|
|
+
|
|
|
+ private boolean mUsingCustomStart;
|
|
|
+
|
|
|
+ private boolean targetScrollWithLayout = true;
|
|
|
+
|
|
|
+ private int pushDistance = 0;
|
|
|
+
|
|
|
+ private CircleProgressView defaultProgressView = null;
|
|
|
+
|
|
|
+ private boolean usingDefaultHeader = true;
|
|
|
+
|
|
|
+ private float density = 1.0f;
|
|
|
+
|
|
|
+ private boolean isProgressEnable = true;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下拉时,超过距离之后,弹回来的动画监听器
|
|
|
+ */
|
|
|
+ private AnimationListener mRefreshListener = new AnimationListener() {
|
|
|
+ @Override
|
|
|
+ public void onAnimationStart(Animation animation) {
|
|
|
+ isProgressEnable = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onAnimationRepeat(Animation animation) {
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onAnimationEnd(Animation animation) {
|
|
|
+ isProgressEnable = true;
|
|
|
+ if (mRefreshing) {
|
|
|
+ if (mNotify) {
|
|
|
+ if (usingDefaultHeader) {
|
|
|
+ ViewCompat.setAlpha(defaultProgressView, 1.0f);
|
|
|
+ defaultProgressView.setOnDraw(true);
|
|
|
+ new Thread(defaultProgressView).start();
|
|
|
+ }
|
|
|
+ if (mListener != null) {
|
|
|
+ mListener.onRefresh();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ mHeadViewContainer.setVisibility(View.GONE);
|
|
|
+ if (mScale) {
|
|
|
+ setAnimationProgress(0);
|
|
|
+ } else {
|
|
|
+ setTargetOffsetTopAndBottom(mOriginalOffsetTop
|
|
|
+ - mCurrentTargetOffsetTop, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ mCurrentTargetOffsetTop = mHeadViewContainer.getTop();
|
|
|
+ updateListenerCallBack();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新回调
|
|
|
+ */
|
|
|
+ private void updateListenerCallBack() {
|
|
|
+ int distance = mCurrentTargetOffsetTop + mHeadViewContainer.getHeight();
|
|
|
+ if (mListener != null) {
|
|
|
+ mListener.onPullDistance(distance);
|
|
|
+ }
|
|
|
+ if (usingDefaultHeader && isProgressEnable) {
|
|
|
+ defaultProgressView.setPullDistance(distance);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加头布局
|
|
|
+ *
|
|
|
+ * @param child
|
|
|
+ */
|
|
|
+ public void setHeaderView(View child) {
|
|
|
+ if (child == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (mHeadViewContainer == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ usingDefaultHeader = false;
|
|
|
+ mHeadViewContainer.removeAllViews();
|
|
|
+ RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
|
|
|
+ mHeaderViewWidth, mHeaderViewHeight);
|
|
|
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
|
|
|
+ mHeadViewContainer.addView(child, layoutParams);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setFooterView(View child) {
|
|
|
+ if (child == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (mFooterViewContainer == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mFooterViewContainer.removeAllViews();
|
|
|
+ RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
|
|
|
+ mFooterViewWidth, mFooterViewHeight);
|
|
|
+ mFooterViewContainer.addView(child, layoutParams);
|
|
|
+ }
|
|
|
+
|
|
|
+ public SuperSwipeRefreshLayout(Context context) {
|
|
|
+ this(context, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("deprecation")
|
|
|
+ public SuperSwipeRefreshLayout(Context context, AttributeSet attrs) {
|
|
|
+ super(context, attrs);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * getScaledTouchSlop是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件
|
|
|
+ */
|
|
|
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
|
|
+
|
|
|
+ mMediumAnimationDuration = getResources().getInteger(
|
|
|
+ android.R.integer.config_mediumAnimTime);
|
|
|
+
|
|
|
+ setWillNotDraw(false);
|
|
|
+ mDecelerateInterpolator = new DecelerateInterpolator(
|
|
|
+ DECELERATE_INTERPOLATION_FACTOR);
|
|
|
+
|
|
|
+ final TypedArray a = context
|
|
|
+ .obtainStyledAttributes(attrs, LAYOUT_ATTRS);
|
|
|
+ setEnabled(a.getBoolean(0, true));
|
|
|
+ a.recycle();
|
|
|
+
|
|
|
+ WindowManager wm = (WindowManager) context
|
|
|
+ .getSystemService(Context.WINDOW_SERVICE);
|
|
|
+ Display display = wm.getDefaultDisplay();
|
|
|
+ final DisplayMetrics metrics = getResources().getDisplayMetrics();
|
|
|
+ mHeaderViewWidth = (int) display.getWidth();
|
|
|
+ mFooterViewWidth = (int) display.getWidth();
|
|
|
+ mHeaderViewHeight = (int) (HEADER_VIEW_HEIGHT * metrics.density);
|
|
|
+ mFooterViewHeight = (int) (HEADER_VIEW_HEIGHT * metrics.density);
|
|
|
+ defaultProgressView = new CircleProgressView(getContext());
|
|
|
+ createHeaderViewContainer();
|
|
|
+ createFooterViewContainer();
|
|
|
+ ViewCompat.setChildrenDrawingOrderEnabled(this, true);
|
|
|
+ mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density;
|
|
|
+ density = metrics.density;
|
|
|
+ mTotalDragDistance = mSpinnerFinalOffset;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 孩子节点绘制的顺序
|
|
|
+ *
|
|
|
+ * @param childCount
|
|
|
+ * @param i
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ protected int getChildDrawingOrder(int childCount, int i) {
|
|
|
+ // 将新添加的View,放到最后绘制
|
|
|
+ if (mHeaderViewIndex < 0 && mFooterViewIndex < 0) {
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+ if (i == childCount - 2) {
|
|
|
+ return mHeaderViewIndex;
|
|
|
+ }
|
|
|
+ if (i == childCount - 1) {
|
|
|
+ return mFooterViewIndex;
|
|
|
+ }
|
|
|
+ int bigIndex = mFooterViewIndex > mHeaderViewIndex ? mFooterViewIndex
|
|
|
+ : mHeaderViewIndex;
|
|
|
+ int smallIndex = mFooterViewIndex < mHeaderViewIndex ? mFooterViewIndex
|
|
|
+ : mHeaderViewIndex;
|
|
|
+ if (i >= smallIndex && i < bigIndex - 1) {
|
|
|
+ return i + 1;
|
|
|
+ }
|
|
|
+ if (i >= bigIndex || (i == bigIndex - 1)) {
|
|
|
+ return i + 2;
|
|
|
+ }
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建头布局的容器
|
|
|
+ */
|
|
|
+ private void createHeaderViewContainer() {
|
|
|
+ RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
|
|
|
+ (int) (mHeaderViewHeight * 0.8),
|
|
|
+ (int) (mHeaderViewHeight * 0.8));
|
|
|
+ layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
|
|
|
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
|
|
|
+ mHeadViewContainer = new HeadViewContainer(getContext());
|
|
|
+ mHeadViewContainer.setVisibility(View.GONE);
|
|
|
+ defaultProgressView.setVisibility(View.VISIBLE);
|
|
|
+ defaultProgressView.setOnDraw(false);
|
|
|
+ mHeadViewContainer.addView(defaultProgressView, layoutParams);
|
|
|
+ addView(mHeadViewContainer);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加底部布局
|
|
|
+ */
|
|
|
+ private void createFooterViewContainer() {
|
|
|
+ mFooterViewContainer = new RelativeLayout(getContext());
|
|
|
+ mFooterViewContainer.setVisibility(View.GONE);
|
|
|
+ addView(mFooterViewContainer);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置
|
|
|
+ *
|
|
|
+ * @param listener
|
|
|
+ */
|
|
|
+ public void setOnPullRefreshListener(OnPullRefreshListener listener) {
|
|
|
+ mListener = listener;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setHeaderViewBackgroundColor(int color) {
|
|
|
+ mHeadViewContainer.setBackgroundColor(color);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置上拉加载更多的接口
|
|
|
+ *
|
|
|
+ * @param onPushLoadMoreListener
|
|
|
+ */
|
|
|
+ public void setOnPushLoadMoreListener(
|
|
|
+ OnPushLoadMoreListener onPushLoadMoreListener) {
|
|
|
+ this.mOnPushLoadMoreListener = onPushLoadMoreListener;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Notify the widget that refresh state has changed. Do not call this when
|
|
|
+ * refresh is triggered by a swipe gesture.
|
|
|
+ *
|
|
|
+ * @param refreshing Whether or not the view should show refresh progress.
|
|
|
+ */
|
|
|
+ public void setRefreshing(boolean refreshing) {
|
|
|
+ if (refreshing && mRefreshing != refreshing) {
|
|
|
+ // scale and show
|
|
|
+ mRefreshing = refreshing;
|
|
|
+ int endTarget = 0;
|
|
|
+ if (!mUsingCustomStart) {
|
|
|
+ endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop);
|
|
|
+ } else {
|
|
|
+ endTarget = (int) mSpinnerFinalOffset;
|
|
|
+ }
|
|
|
+ setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop,
|
|
|
+ true /* requires update */);
|
|
|
+ mNotify = false;
|
|
|
+ startScaleUpAnimation(mRefreshListener);
|
|
|
+ } else {
|
|
|
+ setRefreshing(refreshing, false /* notify */);
|
|
|
+ if (usingDefaultHeader) {
|
|
|
+ defaultProgressView.setOnDraw(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void startScaleUpAnimation(AnimationListener listener) {
|
|
|
+ mHeadViewContainer.setVisibility(View.VISIBLE);
|
|
|
+ mScaleAnimation = new Animation() {
|
|
|
+ @Override
|
|
|
+ public void applyTransformation(float interpolatedTime,
|
|
|
+ Transformation t) {
|
|
|
+ setAnimationProgress(interpolatedTime);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ mScaleAnimation.setDuration(mMediumAnimationDuration);
|
|
|
+ if (listener != null) {
|
|
|
+ mHeadViewContainer.setAnimationListener(listener);
|
|
|
+ }
|
|
|
+ mHeadViewContainer.clearAnimation();
|
|
|
+ mHeadViewContainer.startAnimation(mScaleAnimation);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setAnimationProgress(float progress) {
|
|
|
+ if (!usingDefaultHeader) {
|
|
|
+ progress = 1;
|
|
|
+ }
|
|
|
+ ViewCompat.setScaleX(mHeadViewContainer, progress);
|
|
|
+ ViewCompat.setScaleY(mHeadViewContainer, progress);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setRefreshing(boolean refreshing, final boolean notify) {
|
|
|
+ if (mRefreshing != refreshing) {
|
|
|
+ mNotify = notify;
|
|
|
+ ensureTarget();
|
|
|
+ mRefreshing = refreshing;
|
|
|
+ if (mRefreshing) {
|
|
|
+ animateOffsetToCorrectPosition(mCurrentTargetOffsetTop,
|
|
|
+ mRefreshListener);
|
|
|
+ } else {
|
|
|
+ startScaleDownAnimation(mRefreshListener);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void startScaleDownAnimation(AnimationListener listener) {
|
|
|
+ mScaleDownAnimation = new Animation() {
|
|
|
+ @Override
|
|
|
+ public void applyTransformation(float interpolatedTime,
|
|
|
+ Transformation t) {
|
|
|
+ setAnimationProgress(1 - interpolatedTime);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);
|
|
|
+ mHeadViewContainer.setAnimationListener(listener);
|
|
|
+ mHeadViewContainer.clearAnimation();
|
|
|
+ mHeadViewContainer.startAnimation(mScaleDownAnimation);
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isRefreshing() {
|
|
|
+ return mRefreshing;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 确保mTarget不为空<br>
|
|
|
+ * mTarget一般是可滑动的ScrollView,ListView,RecyclerView等
|
|
|
+ */
|
|
|
+ private void ensureTarget() {
|
|
|
+ if (mTarget == null) {
|
|
|
+ for (int i = 0; i < getChildCount(); i++) {
|
|
|
+ View child = getChildAt(i);
|
|
|
+ if (!child.equals(mHeadViewContainer)
|
|
|
+ && !child.equals(mFooterViewContainer)) {
|
|
|
+ mTarget = child;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set the distance to trigger a sync in dips
|
|
|
+ *
|
|
|
+ * @param distance
|
|
|
+ */
|
|
|
+ public void setDistanceToTriggerSync(int distance) {
|
|
|
+ mTotalDragDistance = distance;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onLayout(boolean changed, int left, int top, int right,
|
|
|
+ int bottom) {
|
|
|
+ final int width = getMeasuredWidth();
|
|
|
+ final int height = getMeasuredHeight();
|
|
|
+ if (getChildCount() == 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (mTarget == null) {
|
|
|
+ ensureTarget();
|
|
|
+ }
|
|
|
+ if (mTarget == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ int distance = mCurrentTargetOffsetTop + mHeadViewContainer.getHeight();
|
|
|
+ if (!targetScrollWithLayout) {
|
|
|
+ // 判断标志位,如果目标View不跟随手指的滑动而滑动,将下拉偏移量设置为0
|
|
|
+ distance = 0;
|
|
|
+ }
|
|
|
+ final View child = mTarget;
|
|
|
+ final int childLeft = getPaddingLeft();
|
|
|
+ final int childTop = getPaddingTop() + distance - pushDistance;// 根据偏移量distance更新
|
|
|
+ final int childWidth = width - getPaddingLeft() - getPaddingRight();
|
|
|
+ final int childHeight = height - getPaddingTop() - getPaddingBottom();
|
|
|
+ Log.d(LOG_TAG, "debug:onLayout childHeight = " + childHeight);
|
|
|
+ child.layout(childLeft, childTop, childLeft + childWidth, childTop
|
|
|
+ + childHeight);// 更新目标View的位置
|
|
|
+ int headViewWidth = mHeadViewContainer.getMeasuredWidth();
|
|
|
+ int headViewHeight = mHeadViewContainer.getMeasuredHeight();
|
|
|
+ mHeadViewContainer.layout((width / 2 - headViewWidth / 2),
|
|
|
+ mCurrentTargetOffsetTop, (width / 2 + headViewWidth / 2),
|
|
|
+ mCurrentTargetOffsetTop + headViewHeight);// 更新头布局的位置
|
|
|
+ int footViewWidth = mFooterViewContainer.getMeasuredWidth();
|
|
|
+ int footViewHeight = mFooterViewContainer.getMeasuredHeight();
|
|
|
+ mFooterViewContainer.layout((width / 2 - footViewWidth / 2), height
|
|
|
+ - pushDistance, (width / 2 + footViewWidth / 2), height
|
|
|
+ + footViewHeight - pushDistance);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
|
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
|
+ if (mTarget == null) {
|
|
|
+ ensureTarget();
|
|
|
+ }
|
|
|
+ if (mTarget == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mTarget.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth()
|
|
|
+ - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY),
|
|
|
+ MeasureSpec.makeMeasureSpec(getMeasuredHeight()
|
|
|
+ - getPaddingTop() - getPaddingBottom(),
|
|
|
+ MeasureSpec.EXACTLY));
|
|
|
+ mHeadViewContainer.measure(MeasureSpec.makeMeasureSpec(
|
|
|
+ mHeaderViewWidth, MeasureSpec.EXACTLY), MeasureSpec
|
|
|
+ .makeMeasureSpec(3 * mHeaderViewHeight, MeasureSpec.EXACTLY));
|
|
|
+ mFooterViewContainer.measure(MeasureSpec.makeMeasureSpec(
|
|
|
+ mFooterViewWidth, MeasureSpec.EXACTLY), MeasureSpec
|
|
|
+ .makeMeasureSpec(mFooterViewHeight, MeasureSpec.EXACTLY));
|
|
|
+ if (!mUsingCustomStart && !mOriginalOffsetCalculated) {
|
|
|
+ mOriginalOffsetCalculated = true;
|
|
|
+ mCurrentTargetOffsetTop = mOriginalOffsetTop = -mHeadViewContainer
|
|
|
+ .getMeasuredHeight();
|
|
|
+ updateListenerCallBack();
|
|
|
+ }
|
|
|
+ mHeaderViewIndex = -1;
|
|
|
+ for (int index = 0; index < getChildCount(); index++) {
|
|
|
+ if (getChildAt(index) == mHeadViewContainer) {
|
|
|
+ mHeaderViewIndex = index;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ mFooterViewIndex = -1;
|
|
|
+ for (int index = 0; index < getChildCount(); index++) {
|
|
|
+ if (getChildAt(index) == mFooterViewContainer) {
|
|
|
+ mFooterViewIndex = index;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断目标View是否滑动到顶部-还能否继续滑动
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public boolean isChildScrollToTop() {
|
|
|
+ if (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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否滑动到底部
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public boolean isChildScrollToBottom() {
|
|
|
+ if (isChildScrollToTop()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (mTarget instanceof RecyclerView) {
|
|
|
+ RecyclerView recyclerView = (RecyclerView) mTarget;
|
|
|
+ LayoutManager layoutManager = recyclerView.getLayoutManager();
|
|
|
+ int count = recyclerView.getAdapter().getItemCount();
|
|
|
+ if (layoutManager instanceof LinearLayoutManager && count > 0) {
|
|
|
+ LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
|
|
|
+ if (linearLayoutManager.findLastCompletelyVisibleItemPosition() == count - 1) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
|
|
+ StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
|
|
|
+ int[] lastItems = new int[2];
|
|
|
+ staggeredGridLayoutManager
|
|
|
+ .findLastCompletelyVisibleItemPositions(lastItems);
|
|
|
+ int lastItem = Math.max(lastItems[0], lastItems[1]);
|
|
|
+ if (lastItem == count - 1) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } else if (mTarget instanceof AbsListView) {
|
|
|
+ final AbsListView absListView = (AbsListView) mTarget;
|
|
|
+ int count = absListView.getAdapter().getCount();
|
|
|
+ int fristPos = absListView.getFirstVisiblePosition();
|
|
|
+ if (fristPos == 0
|
|
|
+ && absListView.getChildAt(0).getTop() >= absListView
|
|
|
+ .getPaddingTop()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ int lastPos = absListView.getLastVisiblePosition();
|
|
|
+ if (lastPos > 0 && count > 0 && lastPos == count - 1) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } else if (mTarget instanceof ScrollView) {
|
|
|
+ ScrollView scrollView = (ScrollView) mTarget;
|
|
|
+ View view = (View) scrollView
|
|
|
+ .getChildAt(scrollView.getChildCount() - 1);
|
|
|
+ if (view != null) {
|
|
|
+ int diff = (view.getBottom() - (scrollView.getHeight() + scrollView
|
|
|
+ .getScrollY()));
|
|
|
+ if (diff == 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 主要判断是否应该拦截子View的事件<br>
|
|
|
+ * 如果拦截,则交给自己的OnTouchEvent处理<br>
|
|
|
+ * 否者,交给子View处理<br>
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
|
+ ensureTarget();
|
|
|
+
|
|
|
+ final int action = MotionEventCompat.getActionMasked(ev);
|
|
|
+
|
|
|
+ if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
|
|
|
+ mReturningToStart = false;
|
|
|
+ }
|
|
|
+ if (!isEnabled() || mReturningToStart || mRefreshing || mLoadMore
|
|
|
+ || (!isChildScrollToTop() && !isChildScrollToBottom())) {
|
|
|
+ // 如果子View可以滑动,不拦截事件,交给子View处理-下拉刷新
|
|
|
+ // 或者子View没有滑动到底部不拦截事件-上拉加载更多
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下拉刷新判断
|
|
|
+ switch (action) {
|
|
|
+ case MotionEvent.ACTION_DOWN:
|
|
|
+ setTargetOffsetTopAndBottom(
|
|
|
+ mOriginalOffsetTop - mHeadViewContainer.getTop(), true);// 恢复HeaderView的初始位置
|
|
|
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
|
|
|
+ mIsBeingDragged = false;
|
|
|
+ final float initialMotionY = getMotionEventY(ev, mActivePointerId);
|
|
|
+ if (initialMotionY == -1) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ mInitialMotionY = initialMotionY;// 记录按下的位置
|
|
|
+
|
|
|
+ case MotionEvent.ACTION_MOVE:
|
|
|
+ if (mActivePointerId == INVALID_POINTER) {
|
|
|
+ Log.e(LOG_TAG,
|
|
|
+ "Got ACTION_MOVE event but don't have an active pointer id.");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ final float y = getMotionEventY(ev, mActivePointerId);
|
|
|
+ if (y == -1) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ float yDiff = 0;
|
|
|
+ if (isChildScrollToBottom()) {
|
|
|
+ yDiff = mInitialMotionY - y;// 计算上拉距离
|
|
|
+ if (yDiff > mTouchSlop && !mIsBeingDragged) {// 判断是否下拉的距离足够
|
|
|
+ mIsBeingDragged = true;// 正在上拉
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ yDiff = y - mInitialMotionY;// 计算下拉距离
|
|
|
+ if (yDiff > mTouchSlop && !mIsBeingDragged) {// 判断是否下拉的距离足够
|
|
|
+ mIsBeingDragged = true;// 正在下拉
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case MotionEventCompat.ACTION_POINTER_UP:
|
|
|
+ onSecondaryPointerUp(ev);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case MotionEvent.ACTION_UP:
|
|
|
+ case MotionEvent.ACTION_CANCEL:
|
|
|
+ mIsBeingDragged = false;
|
|
|
+ mActivePointerId = INVALID_POINTER;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return mIsBeingDragged;// 如果正在拖动,则拦截子View的事件
|
|
|
+ }
|
|
|
+
|
|
|
+ private float getMotionEventY(MotionEvent ev, int activePointerId) {
|
|
|
+ final int index = MotionEventCompat.findPointerIndex(ev,
|
|
|
+ activePointerId);
|
|
|
+ if (index < 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ return MotionEventCompat.getY(ev, index);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void requestDisallowInterceptTouchEvent(boolean b) {
|
|
|
+ // Nope.
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onTouchEvent(MotionEvent ev) {
|
|
|
+ final int action = MotionEventCompat.getActionMasked(ev);
|
|
|
+
|
|
|
+ if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
|
|
|
+ mReturningToStart = false;
|
|
|
+ }
|
|
|
+ if (!isEnabled() || mReturningToStart
|
|
|
+ || (!isChildScrollToTop() && !isChildScrollToBottom())) {
|
|
|
+ // 如果子View可以滑动,不拦截事件,交给子View处理
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isChildScrollToBottom()) {// 上拉加载更多
|
|
|
+ return handlerPushTouchEvent(ev, action);
|
|
|
+ } else {// 下拉刷新
|
|
|
+ return handlerPullTouchEvent(ev, action);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean handlerPullTouchEvent(MotionEvent ev, int action) {
|
|
|
+ switch (action) {
|
|
|
+ case MotionEvent.ACTION_DOWN:
|
|
|
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
|
|
|
+ mIsBeingDragged = false;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case MotionEvent.ACTION_MOVE: {
|
|
|
+ final int pointerIndex = MotionEventCompat.findPointerIndex(ev,
|
|
|
+ mActivePointerId);
|
|
|
+ if (pointerIndex < 0) {
|
|
|
+ Log.e(LOG_TAG,
|
|
|
+ "Got ACTION_MOVE event but have an invalid active pointer id.");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ final float y = MotionEventCompat.getY(ev, pointerIndex);
|
|
|
+ final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
|
|
|
+ if (mIsBeingDragged) {
|
|
|
+ float originalDragPercent = overscrollTop / mTotalDragDistance;
|
|
|
+ if (originalDragPercent < 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
|
|
|
+ float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
|
|
|
+ float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset
|
|
|
+ - mOriginalOffsetTop : mSpinnerFinalOffset;
|
|
|
+ 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 = mOriginalOffsetTop
|
|
|
+ + (int) ((slingshotDist * dragPercent) + extraMove);
|
|
|
+ if (mHeadViewContainer.getVisibility() != View.VISIBLE) {
|
|
|
+ mHeadViewContainer.setVisibility(View.VISIBLE);
|
|
|
+ }
|
|
|
+ if (!mScale) {
|
|
|
+ ViewCompat.setScaleX(mHeadViewContainer, 1f);
|
|
|
+ ViewCompat.setScaleY(mHeadViewContainer, 1f);
|
|
|
+ }
|
|
|
+ if (usingDefaultHeader) {
|
|
|
+ float alpha = overscrollTop / mTotalDragDistance;
|
|
|
+ if (alpha >= 1.0f) {
|
|
|
+ alpha = 1.0f;
|
|
|
+ }
|
|
|
+ ViewCompat.setScaleX(defaultProgressView, alpha);
|
|
|
+ ViewCompat.setScaleY(defaultProgressView, alpha);
|
|
|
+ ViewCompat.setAlpha(defaultProgressView, alpha);
|
|
|
+ }
|
|
|
+ if (overscrollTop < mTotalDragDistance) {
|
|
|
+ if (mScale) {
|
|
|
+ setAnimationProgress(overscrollTop / mTotalDragDistance);
|
|
|
+ }
|
|
|
+ if (mListener != null) {
|
|
|
+ mListener.onPullEnable(false);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (mListener != null) {
|
|
|
+ mListener.onPullEnable(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop,
|
|
|
+ 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) {
|
|
|
+ if (action == MotionEvent.ACTION_UP) {
|
|
|
+ Log.e(LOG_TAG,
|
|
|
+ "Got ACTION_UP event but don't have an active pointer id.");
|
|
|
+ }
|
|
|
+ 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 /* notify */);
|
|
|
+ } else {
|
|
|
+ mRefreshing = false;
|
|
|
+ AnimationListener listener = null;
|
|
|
+ if (!mScale) {
|
|
|
+ listener = new AnimationListener() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onAnimationStart(Animation animation) {
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onAnimationEnd(Animation animation) {
|
|
|
+ if (!mScale) {
|
|
|
+ startScaleDownAnimation(null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onAnimationRepeat(Animation animation) {
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+ }
|
|
|
+ animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
|
|
|
+ }
|
|
|
+ mActivePointerId = INVALID_POINTER;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理上拉加载更多的Touch事件
|
|
|
+ *
|
|
|
+ * @param ev
|
|
|
+ * @param action
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private boolean handlerPushTouchEvent(MotionEvent ev, int action) {
|
|
|
+ switch (action) {
|
|
|
+ case MotionEvent.ACTION_DOWN:
|
|
|
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
|
|
|
+ mIsBeingDragged = false;
|
|
|
+ Log.d(LOG_TAG, "debug:onTouchEvent ACTION_DOWN");
|
|
|
+ break;
|
|
|
+ case MotionEvent.ACTION_MOVE: {
|
|
|
+ final int pointerIndex = MotionEventCompat.findPointerIndex(ev,
|
|
|
+ mActivePointerId);
|
|
|
+ if (pointerIndex < 0) {
|
|
|
+ Log.e(LOG_TAG,
|
|
|
+ "Got ACTION_MOVE event but have an invalid active pointer id.");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ final float y = MotionEventCompat.getY(ev, pointerIndex);
|
|
|
+ final float overscrollBottom = (mInitialMotionY - y) * DRAG_RATE;
|
|
|
+ if (mIsBeingDragged) {
|
|
|
+ pushDistance = (int) overscrollBottom;
|
|
|
+ updateFooterViewPosition();
|
|
|
+ if (mOnPushLoadMoreListener != null) {
|
|
|
+ mOnPushLoadMoreListener
|
|
|
+ .onPushEnable(pushDistance >= mFooterViewHeight);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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) {
|
|
|
+ if (action == MotionEvent.ACTION_UP) {
|
|
|
+ Log.e(LOG_TAG,
|
|
|
+ "Got ACTION_UP event but don't have an active pointer id.");
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ final int pointerIndex = MotionEventCompat.findPointerIndex(ev,
|
|
|
+ mActivePointerId);
|
|
|
+ final float y = MotionEventCompat.getY(ev, pointerIndex);
|
|
|
+ final float overscrollBottom = (mInitialMotionY - y) * DRAG_RATE;// 松手是下拉的距离
|
|
|
+ mIsBeingDragged = false;
|
|
|
+ mActivePointerId = INVALID_POINTER;
|
|
|
+ if (overscrollBottom < mFooterViewHeight
|
|
|
+ || mOnPushLoadMoreListener == null) {// 直接取消
|
|
|
+ pushDistance = 0;
|
|
|
+ } else {// 下拉到mFooterViewHeight
|
|
|
+ pushDistance = mFooterViewHeight;
|
|
|
+ }
|
|
|
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
|
|
+ updateFooterViewPosition();
|
|
|
+ if (pushDistance == mFooterViewHeight
|
|
|
+ && mOnPushLoadMoreListener != null) {
|
|
|
+ mLoadMore = true;
|
|
|
+ mOnPushLoadMoreListener.onLoadMore();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ animatorFooterToBottom((int) overscrollBottom, pushDistance);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 松手之后,使用动画将Footer从距离start变化到end
|
|
|
+ *
|
|
|
+ * @param start
|
|
|
+ * @param end
|
|
|
+ */
|
|
|
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
|
|
+ private void animatorFooterToBottom(int start, final int end) {
|
|
|
+ ValueAnimator valueAnimator = ValueAnimator.ofInt(start, end);
|
|
|
+ valueAnimator.setDuration(150);
|
|
|
+ valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
|
|
+ // update
|
|
|
+ pushDistance = (Integer) valueAnimator.getAnimatedValue();
|
|
|
+ updateFooterViewPosition();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
|
|
|
+ @Override
|
|
|
+ public void onAnimationEnd(Animator animation) {
|
|
|
+ if (end > 0 && mOnPushLoadMoreListener != null) {
|
|
|
+ // start loading more
|
|
|
+ mLoadMore = true;
|
|
|
+ mOnPushLoadMoreListener.onLoadMore();
|
|
|
+ } else {
|
|
|
+ resetTargetLayout();
|
|
|
+ mLoadMore = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ valueAnimator.setInterpolator(mDecelerateInterpolator);
|
|
|
+ valueAnimator.start();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置停止加载
|
|
|
+ *
|
|
|
+ * @param loadMore
|
|
|
+ */
|
|
|
+ public void setLoadMore(boolean loadMore) {
|
|
|
+ if (!loadMore && mLoadMore) {// 停止加载
|
|
|
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
|
|
+ mLoadMore = false;
|
|
|
+ pushDistance = 0;
|
|
|
+ updateFooterViewPosition();
|
|
|
+ } else {
|
|
|
+ animatorFooterToBottom(mFooterViewHeight, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void animateOffsetToCorrectPosition(int from,
|
|
|
+ AnimationListener listener) {
|
|
|
+ mFrom = from;
|
|
|
+ mAnimateToCorrectPosition.reset();
|
|
|
+ mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
|
|
|
+ mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
|
|
|
+ if (listener != null) {
|
|
|
+ mHeadViewContainer.setAnimationListener(listener);
|
|
|
+ }
|
|
|
+ mHeadViewContainer.clearAnimation();
|
|
|
+ mHeadViewContainer.startAnimation(mAnimateToCorrectPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void animateOffsetToStartPosition(int from,
|
|
|
+ AnimationListener listener) {
|
|
|
+ if (mScale) {
|
|
|
+ startScaleDownReturnToStartAnimation(from, listener);
|
|
|
+ } else {
|
|
|
+ mFrom = from;
|
|
|
+ mAnimateToStartPosition.reset();
|
|
|
+ mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);
|
|
|
+ mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
|
|
|
+ if (listener != null) {
|
|
|
+ mHeadViewContainer.setAnimationListener(listener);
|
|
|
+ }
|
|
|
+ mHeadViewContainer.clearAnimation();
|
|
|
+ mHeadViewContainer.startAnimation(mAnimateToStartPosition);
|
|
|
+ }
|
|
|
+ resetTargetLayoutDelay(ANIMATE_TO_START_DURATION);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重置Target位置
|
|
|
+ *
|
|
|
+ * @param delay
|
|
|
+ */
|
|
|
+ public void resetTargetLayoutDelay(int delay) {
|
|
|
+ new Handler().postDelayed(new Runnable() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ resetTargetLayout();
|
|
|
+ }
|
|
|
+ }, delay);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重置Target的位置
|
|
|
+ */
|
|
|
+ public void resetTargetLayout() {
|
|
|
+ final int width = getMeasuredWidth();
|
|
|
+ final int height = getMeasuredHeight();
|
|
|
+ final View child = mTarget;
|
|
|
+ final int childLeft = getPaddingLeft();
|
|
|
+ final int childTop = getPaddingTop();
|
|
|
+ final int childWidth = child.getWidth() - getPaddingLeft()
|
|
|
+ - getPaddingRight();
|
|
|
+ final int childHeight = child.getHeight() - getPaddingTop()
|
|
|
+ - getPaddingBottom();
|
|
|
+ child.layout(childLeft, childTop, childLeft + childWidth, childTop
|
|
|
+ + childHeight);
|
|
|
+
|
|
|
+ int headViewWidth = mHeadViewContainer.getMeasuredWidth();
|
|
|
+ int headViewHeight = mHeadViewContainer.getMeasuredHeight();
|
|
|
+ mHeadViewContainer.layout((width / 2 - headViewWidth / 2),
|
|
|
+ -headViewHeight, (width / 2 + headViewWidth / 2), 0);// 更新头布局的位置
|
|
|
+ int footViewWidth = mFooterViewContainer.getMeasuredWidth();
|
|
|
+ int footViewHeight = mFooterViewContainer.getMeasuredHeight();
|
|
|
+ mFooterViewContainer.layout((width / 2 - footViewWidth / 2), height,
|
|
|
+ (width / 2 + footViewWidth / 2), height + footViewHeight);
|
|
|
+ }
|
|
|
+
|
|
|
+ private final Animation mAnimateToCorrectPosition = new Animation() {
|
|
|
+ @Override
|
|
|
+ public void applyTransformation(float interpolatedTime, Transformation t) {
|
|
|
+ int targetTop = 0;
|
|
|
+ int endTarget = 0;
|
|
|
+ if (!mUsingCustomStart) {
|
|
|
+ endTarget = (int) (mSpinnerFinalOffset - Math
|
|
|
+ .abs(mOriginalOffsetTop));
|
|
|
+ } else {
|
|
|
+ endTarget = (int) mSpinnerFinalOffset;
|
|
|
+ }
|
|
|
+ targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
|
|
|
+ int offset = targetTop - mHeadViewContainer.getTop();
|
|
|
+ setTargetOffsetTopAndBottom(offset, false /* requires update */);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setAnimationListener(AnimationListener listener) {
|
|
|
+ super.setAnimationListener(listener);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ private void moveToStart(float interpolatedTime) {
|
|
|
+ int targetTop = 0;
|
|
|
+ targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
|
|
|
+ int offset = targetTop - mHeadViewContainer.getTop();
|
|
|
+ setTargetOffsetTopAndBottom(offset, false /* requires update */);
|
|
|
+ }
|
|
|
+
|
|
|
+ private final Animation mAnimateToStartPosition = new Animation() {
|
|
|
+ @Override
|
|
|
+ public void applyTransformation(float interpolatedTime, Transformation t) {
|
|
|
+ moveToStart(interpolatedTime);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ private void startScaleDownReturnToStartAnimation(int from,
|
|
|
+ AnimationListener listener) {
|
|
|
+ mFrom = from;
|
|
|
+ mStartingScale = ViewCompat.getScaleX(mHeadViewContainer);
|
|
|
+ mScaleDownToStartAnimation = new Animation() {
|
|
|
+ @Override
|
|
|
+ public void applyTransformation(float interpolatedTime,
|
|
|
+ Transformation t) {
|
|
|
+ float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime));
|
|
|
+ setAnimationProgress(targetScale);
|
|
|
+ moveToStart(interpolatedTime);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION);
|
|
|
+ if (listener != null) {
|
|
|
+ mHeadViewContainer.setAnimationListener(listener);
|
|
|
+ }
|
|
|
+ mHeadViewContainer.clearAnimation();
|
|
|
+ mHeadViewContainer.startAnimation(mScaleDownToStartAnimation);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) {
|
|
|
+ mHeadViewContainer.bringToFront();
|
|
|
+ mHeadViewContainer.offsetTopAndBottom(offset);
|
|
|
+ mCurrentTargetOffsetTop = mHeadViewContainer.getTop();
|
|
|
+ if (requiresUpdate && Build.VERSION.SDK_INT < 11) {
|
|
|
+ invalidate();
|
|
|
+ }
|
|
|
+ updateListenerCallBack();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 修改底部布局的位置-敏感pushDistance
|
|
|
+ */
|
|
|
+ private void updateFooterViewPosition() {
|
|
|
+ mFooterViewContainer.setVisibility(View.VISIBLE);
|
|
|
+ mFooterViewContainer.bringToFront();
|
|
|
+ mFooterViewContainer.offsetTopAndBottom(-pushDistance);
|
|
|
+ updatePushDistanceListener();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updatePushDistanceListener() {
|
|
|
+ if (mOnPushLoadMoreListener != null) {
|
|
|
+ mOnPushLoadMoreListener.onPushDistance(pushDistance);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @Description 下拉刷新布局头部的容器
|
|
|
+ */
|
|
|
+ private class HeadViewContainer extends RelativeLayout {
|
|
|
+
|
|
|
+ private AnimationListener mListener;
|
|
|
+
|
|
|
+ public HeadViewContainer(Context context) {
|
|
|
+ super(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setAnimationListener(AnimationListener listener) {
|
|
|
+ mListener = listener;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onAnimationStart() {
|
|
|
+ super.onAnimationStart();
|
|
|
+ if (mListener != null) {
|
|
|
+ mListener.onAnimationStart(getAnimation());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onAnimationEnd() {
|
|
|
+ super.onAnimationEnd();
|
|
|
+ if (mListener != null) {
|
|
|
+ mListener.onAnimationEnd(getAnimation());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断子View是否跟随手指的滑动而滑动,默认跟随
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public boolean isTargetScrollWithLayout() {
|
|
|
+ return targetScrollWithLayout;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置子View是否跟谁手指的滑动而滑动
|
|
|
+ *
|
|
|
+ * @param targetScrollWithLayout
|
|
|
+ */
|
|
|
+ public void setTargetScrollWithLayout(boolean targetScrollWithLayout) {
|
|
|
+ this.targetScrollWithLayout = targetScrollWithLayout;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下拉刷新回调
|
|
|
+ */
|
|
|
+ public interface OnPullRefreshListener {
|
|
|
+ public void onRefresh();
|
|
|
+
|
|
|
+ public void onPullDistance(int distance);
|
|
|
+
|
|
|
+ public void onPullEnable(boolean enable);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 上拉加载更多
|
|
|
+ */
|
|
|
+ public interface OnPushLoadMoreListener {
|
|
|
+ public void onLoadMore();
|
|
|
+
|
|
|
+ public void onPushDistance(int distance);
|
|
|
+
|
|
|
+ public void onPushEnable(boolean enable);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Adapter
|
|
|
+ */
|
|
|
+ public class OnPullRefreshListenerAdapter implements OnPullRefreshListener {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onRefresh() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onPullDistance(int distance) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onPullEnable(boolean enable) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public class OnPushLoadMoreListenerAdapter implements
|
|
|
+ OnPushLoadMoreListener {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onLoadMore() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onPushDistance(int distance) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onPushEnable(boolean enable) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置默认下拉刷新进度条的颜色
|
|
|
+ *
|
|
|
+ * @param color
|
|
|
+ */
|
|
|
+ public void setDefaultCircleProgressColor(int color) {
|
|
|
+ if (usingDefaultHeader) {
|
|
|
+ defaultProgressView.setProgressColor(color);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置圆圈的背景色
|
|
|
+ *
|
|
|
+ * @param color
|
|
|
+ */
|
|
|
+ public void setDefaultCircleBackgroundColor(int color) {
|
|
|
+ if (usingDefaultHeader) {
|
|
|
+ defaultProgressView.setCircleBackgroundColor(color);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setDefaultCircleShadowColor(int color) {
|
|
|
+ if (usingDefaultHeader) {
|
|
|
+ defaultProgressView.setShadowColor(color);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 默认的下拉刷新样式
|
|
|
+ */
|
|
|
+ public class CircleProgressView extends View implements Runnable {
|
|
|
+
|
|
|
+ private static final int PEROID = 16;// 绘制周期
|
|
|
+ private Paint progressPaint;
|
|
|
+ private Paint bgPaint;
|
|
|
+ private int width;// view的高度
|
|
|
+ private int height;// view的宽度
|
|
|
+
|
|
|
+ private boolean isOnDraw = false;
|
|
|
+ private boolean isRunning = false;
|
|
|
+ private int startAngle = 0;
|
|
|
+ private int speed = 8;
|
|
|
+ private RectF ovalRect = null;
|
|
|
+ private RectF bgRect = null;
|
|
|
+ private int swipeAngle;
|
|
|
+ private int progressColor = 0xffcccccc;
|
|
|
+ private int circleBackgroundColor = 0xffffffff;
|
|
|
+ private int shadowColor = 0xff999999;
|
|
|
+
|
|
|
+ public CircleProgressView(Context context) {
|
|
|
+ super(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ public CircleProgressView(Context context, AttributeSet attrs) {
|
|
|
+ super(context, attrs);
|
|
|
+ }
|
|
|
+
|
|
|
+ public CircleProgressView(Context context, AttributeSet attrs,
|
|
|
+ int defStyleAttr) {
|
|
|
+ super(context, attrs, defStyleAttr);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onDraw(Canvas canvas) {
|
|
|
+ super.onDraw(canvas);
|
|
|
+ canvas.drawArc(getBgRect(), 0, 360, false, createBgPaint());
|
|
|
+ int index = startAngle / 360;
|
|
|
+ if (index % 2 == 0) {
|
|
|
+ swipeAngle = (startAngle % 720) / 2;
|
|
|
+ } else {
|
|
|
+ swipeAngle = 360 - (startAngle % 720) / 2;
|
|
|
+ }
|
|
|
+ canvas.drawArc(getOvalRect(), startAngle, swipeAngle, false,
|
|
|
+ createPaint());
|
|
|
+ }
|
|
|
+
|
|
|
+ private RectF getBgRect() {
|
|
|
+ width = getWidth();
|
|
|
+ height = getHeight();
|
|
|
+ if (bgRect == null) {
|
|
|
+ int offset = (int) (density * 2);
|
|
|
+ bgRect = new RectF(offset, offset, width - offset, height
|
|
|
+ - offset);
|
|
|
+ }
|
|
|
+ return bgRect;
|
|
|
+ }
|
|
|
+
|
|
|
+ private RectF getOvalRect() {
|
|
|
+ width = getWidth();
|
|
|
+ height = getHeight();
|
|
|
+ if (ovalRect == null) {
|
|
|
+ int offset = (int) (density * 8);
|
|
|
+ ovalRect = new RectF(offset, offset, width - offset, height
|
|
|
+ - offset);
|
|
|
+ }
|
|
|
+ return ovalRect;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setProgressColor(int progressColor) {
|
|
|
+ this.progressColor = progressColor;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setCircleBackgroundColor(int circleBackgroundColor) {
|
|
|
+ this.circleBackgroundColor = circleBackgroundColor;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setShadowColor(int shadowColor) {
|
|
|
+ this.shadowColor = shadowColor;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据画笔的颜色,创建画笔
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Paint createPaint() {
|
|
|
+ if (this.progressPaint == null) {
|
|
|
+ progressPaint = new Paint();
|
|
|
+ progressPaint.setStrokeWidth((int) (density * 3));
|
|
|
+ progressPaint.setStyle(Paint.Style.STROKE);
|
|
|
+ progressPaint.setAntiAlias(true);
|
|
|
+ }
|
|
|
+ progressPaint.setColor(progressColor);
|
|
|
+ return progressPaint;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Paint createBgPaint() {
|
|
|
+ if (this.bgPaint == null) {
|
|
|
+ bgPaint = new Paint();
|
|
|
+ bgPaint.setColor(circleBackgroundColor);
|
|
|
+ bgPaint.setStyle(Paint.Style.FILL);
|
|
|
+ bgPaint.setAntiAlias(true);
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
|
|
+ this.setLayerType(LAYER_TYPE_SOFTWARE, bgPaint);
|
|
|
+ }
|
|
|
+ bgPaint.setShadowLayer(4.0f, 0.0f, 2.0f, shadowColor);
|
|
|
+ }
|
|
|
+ return bgPaint;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setPullDistance(int distance) {
|
|
|
+ this.startAngle = distance * 2;
|
|
|
+ postInvalidate();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ while (isOnDraw) {
|
|
|
+ isRunning = true;
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ startAngle += speed;
|
|
|
+ postInvalidate();
|
|
|
+ long time = System.currentTimeMillis() - startTime;
|
|
|
+ if (time < PEROID) {
|
|
|
+ try {
|
|
|
+ Thread.sleep(PEROID - time);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setOnDraw(boolean isOnDraw) {
|
|
|
+ this.isOnDraw = isOnDraw;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setSpeed(int speed) {
|
|
|
+ this.speed = speed;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isRunning() {
|
|
|
+ return isRunning;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
|
|
|
+ super.onWindowFocusChanged(hasWindowFocus);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onDetachedFromWindow() {
|
|
|
+ isOnDraw = false;
|
|
|
+ super.onDetachedFromWindow();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|