SwipeRefreshLayout源码分析
来源:互联网 发布:淘宝有ego女装官网吗? 编辑:程序博客网 时间:2024/06/15 10:35
public class SwipeRefreshLayout extends ViewGroup implements NestedScrollingParent, NestedScrollingChild { // 这两个变量决定下拉刷新的刷新图标的大小,一个标准图,一个大图 public static final int LARGE = MaterialProgressDrawable.LARGE; public static final int DEFAULT = MaterialProgressDrawable.DEFAULT; ... // 这个view 就是包裹的子view , 通过这个view来判断是否达到下拉刷新的条件,这个view默认也是要可以滑动才行 private View mTarget; // the target of the gesture private OnRefreshListener mListener; private boolean mRefreshing = false; private int mTouchSlop; private float mTotalDragDistance = -1; ... //这个CircleImageView 就是下拉刷新做动画的控件 private CircleImageView mCircleView; private int mCircleViewIndex = -1; protected int mFrom; private float mStartingScale; protected int mOriginalOffsetTop; //这个drawable是CircleImageView里面的图 private MaterialProgressDrawable mProgress; private Animation mScaleAnimation; private Animation mScaleDownAnimation; private Animation mAlphaStartAnimation; private Animation mAlphaMaxAnimation; private Animation mScaleDownToStartAnimation; private float mSpinnerFinalOffset; private boolean mNotify; private int mCircleWidth; private int mCircleHeight; // Whether the client has set a custom starting position; private boolean mUsingCustomStart; //非常重要的监听器,当动画结束的时候会回调刷新,这个onRefresh里面提供给客户端重载数据,然后再 setRefreshing(false),代表数据更新完毕 private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { if (mRefreshing) { // Make sure the progress view is fully visible mProgress.setAlpha(MAX_ALPHA); mProgress.start(); if (mNotify) { if (mListener != null) { mListener.onRefresh(); } } mCurrentTargetOffsetTop = mCircleView.getTop(); } else { reset(); } } }; /** * The refresh indicator starting and resting position is always positioned * near the top of the refreshing content. This position is a consistent * location, but can be adjusted in either direction based on whether or not * there is a toolbar or actionbar present. * * @param scale Set to true if there is no view at a higher z-order than * where the progress spinner is set to appear. * @param start The offset in pixels from the top of this view at which the * progress spinner should appear. * @param end The offset in pixels from the top of this view at which the * progress spinner should come to rest after a successful swipe * gesture. */ public void setProgressViewOffset(boolean scale, int start, int end) { mScale = scale; mCircleView.setVisibility(View.GONE); mOriginalOffsetTop = mCurrentTargetOffsetTop = start; mSpinnerFinalOffset = end; mUsingCustomStart = true; mCircleView.invalidate(); } /** * The refresh indicator resting position is always positioned near the top * of the refreshing content. This position is a consistent location, but * can be adjusted in either direction based on whether or not there is a * toolbar or actionbar present. * * @param scale Set to true if there is no view at a higher z-order than * where the progress spinner is set to appear. * @param end The offset in pixels from the top of this view at which the * progress spinner should come to rest after a successful swipe * gesture. */ public void setProgressViewEndTarget(boolean scale, int end) { mSpinnerFinalOffset = end; mScale = scale; mCircleView.invalidate(); } private void createProgressView() { mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER/2); mProgress = new MaterialProgressDrawable(getContext(), this); mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); mCircleView.setImageDrawable(mProgress); mCircleView.setVisibility(View.GONE); addView(mCircleView); } /** * Set the listener to be notified when a refresh is triggered via the swipe * gesture. */ public void setOnRefreshListener(OnRefreshListener listener) { mListener = listener; } /** * 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. */ //一般提供给客户端调用传false,结束下拉刷新 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 */); } } 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(Animation.AnimationListener listener) { mScaleDownAnimation = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { setAnimationProgress(1 - interpolatedTime); } }; mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION); mCircleView.setAnimationListener(listener); mCircleView.clearAnimation(); mCircleView.startAnimation(mScaleDownAnimation); } private void startProgressAlphaStartAnimation() { mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA); } private void startProgressAlphaMaxAnimation() { mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA); } private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) { // Pre API 11, alpha is used in place of scale. Don't also use it to // show the trigger point. if (mScale && isAlphaUsedForScale()) { return null; } Animation alpha = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { mProgress .setAlpha((int) (startingAlpha+ ((endingAlpha - startingAlpha) * interpolatedTime))); } }; alpha.setDuration(ALPHA_ANIMATION_DURATION); // Clear out the previous animation listeners. mCircleView.setAnimationListener(null); mCircleView.clearAnimation(); mCircleView.startAnimation(alpha); return alpha; } /** * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)} */ @Deprecated public void setProgressBackgroundColor(int colorRes) { setProgressBackgroundColorSchemeResource(colorRes); } /** * Set the background color of the progress spinner disc. * * @param colorRes Resource id of the color. */ public void setProgressBackgroundColorSchemeResource(@ColorRes int colorRes) { setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes)); } /** * Set the background color of the progress spinner disc. * * @param color */ public void setProgressBackgroundColorSchemeColor(@ColorInt int color) { mCircleView.setBackgroundColor(color); mProgress.setBackgroundColor(color); } /** * @deprecated Use {@link #setColorSchemeResources(int...)} */ @Deprecated public void setColorScheme(@ColorInt int... colors) { setColorSchemeResources(colors); } /** * Set the color resources used in the progress animation from color resources. * The first color will also be the color of the bar that grows in response * to a user swipe gesture. * * @param colorResIds */ //从解析可以得知,通过这个方法参数可以传递一系列的颜色资源,保存在colorRes[]里面 public void setColorSchemeResources(@ColorRes int... colorResIds) { final Resources res = getResources(); int[] colorRes = new int[colorResIds.length]; for (int i = 0; i < colorResIds.length; i++) { colorRes[i] = res.getColor(colorResIds[i]); } //通过下面这个方法设置颜色 setColorSchemeColors(colorRes); } /** * Set the colors used in the progress animation. The first * color will also be the color of the bar that grows in response to a user * swipe gesture. * * @param colors */ public void setColorSchemeColors(@ColorInt int... colors) { //设置目标target,也就是子view ensureTarget(); //这个颜色资源数组最后设置到mProgress上 mProgress.setColorSchemeColors(colors); } /** * @return Whether the SwipeRefreshWidget is actively showing refresh * progress. */ //这个方法判断是否正在刷新 public boolean isRefreshing() { return mRefreshing; } //比较重要的方法,遍历所有child,如果发现child不是mCircleView(下拉做动画的控件),就设置mTarget private void ensureTarget() { // Don't bother getting the parent height if the parent hasn't been laid // out yet. if (mTarget == null) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (!child.equals(mCircleView)) { mTarget = child; break; } } } } /** * @return Whether it is possible for the child view of this layout to * scroll up. Override this if the child view is a custom view. */ public 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 ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0; } } else { return ViewCompat.canScrollVertically(mTarget, -1); } } @Override //通过这个方法,看什么时候需要打断touch时间,自己消耗这个touch,这个控件是下拉刷新,那么按道理是滑动到顶部的时候,下拉要打断操作,其余默认交给子view public boolean onInterceptTouchEvent(MotionEvent ev) { ensureTarget(); final int action = MotionEventCompat.getActionMasked(ev); if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { mReturningToStart = false; } //主要通过这几个方法和变量确定 //1.isEnabled 代表SwipeRefreshLayout是否启用 //2.canChildScrollUp()表明是否还能继续向上划 //3.mRefreshing 代表当前状态,mRefreshing = false ,代表没有在刷新 //4.mNestedScrollInProgress代表滚动状态 //5.mReturningToStart暂时看不到有设置为true的情况,有待查看 if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing || mNestedScrollInProgress) { // Fail fast if we're not in a state where a swipe is possible return false; } switch (action) { case MotionEvent.ACTION_DOWN: setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true); mActivePointerId = MotionEventCompat.getPointerId(ev, 0); //先在ACTION_DOWN时,设置mIsBeingDragged ,代表还未算是拖拽状态 mIsBeingDragged = false; final float initialDownY = getMotionEventY(ev, mActivePointerId); if (initialDownY == -1) { return false; } mInitialDownY = initialDownY; break; 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; } final float yDiff = y - mInitialDownY; //判断距离,大于mTouchSlop ,拖拽成立,mIsBeingDragged = true,代表打断touch事件 //传递到当前的onTouchEvent()方法中 if (yDiff > mTouchSlop && !mIsBeingDragged) { mInitialMotionY = mInitialDownY + mTouchSlop; mIsBeingDragged = true; mProgress.setAlpha(STARTING_PROGRESS_ALPHA); } 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; } private void moveSpinner(float overscrollTop) { mProgress.showArrow(true); float originalDragPercent = overscrollTop / mTotalDragDistance; float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; 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); // where 1.0f is a full circle if (mCircleView.getVisibility() != View.VISIBLE) { mCircleView.setVisibility(View.VISIBLE); } if (!mScale) { ViewCompat.setScaleX(mCircleView, 1f); ViewCompat.setScaleY(mCircleView, 1f); } if (mScale) { setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance)); } if (overscrollTop < mTotalDragDistance) { if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA && !isAnimationRunning(mAlphaStartAnimation)) { // Animate the alpha startProgressAlphaStartAnimation(); } } else { if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) { // Animate the alpha startProgressAlphaMaxAnimation(); } } float strokeStart = adjustedPercent * .8f; mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); mProgress.setArrowScale(Math.min(1f, adjustedPercent)); float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; mProgress.setProgressRotation(rotation); setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */); } //若果当前距离超过mTotalDragDistance,代表刷新状态成立,开始刷新 //若果距离不够,,做了一个动画回滚回去 private void finishSpinner(float overscrollTop) { if (overscrollTop > mTotalDragDistance) { setRefreshing(true, true /* notify */); } else { // cancel refresh mRefreshing = false; mProgress.setStartEndTrim(0f, 0f); Animation.AnimationListener listener = null; if (!mScale) { listener = new Animation.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); mProgress.showArrow(false); } } @Override public boolean onTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); int pointerIndex = -1; if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { mReturningToStart = false; } if (!isEnabled() || mReturningToStart || canChildScrollUp() || mNestedScrollInProgress) { // Fail fast if we're not in a state where a swipe is possible return false; } switch (action) { case MotionEvent.ACTION_DOWN: mActivePointerId = MotionEventCompat.getPointerId(ev, 0); mIsBeingDragged = false; break; case MotionEvent.ACTION_MOVE: { 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) { if (overscrollTop > 0) { //根据当前拖动的距离,调整下拉刷新控件的位置 moveSpinner(overscrollTop); } else { return false; } } break; } case MotionEvent.ACTION_UP: { pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); if (pointerIndex < 0) { Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id."); return false; } final float y = MotionEventCompat.getY(ev, pointerIndex); final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; mIsBeingDragged = false; //根据拖拽的距离判断是否回滚还是刷新子view finishSpinner(overscrollTop); mActivePointerId = INVALID_POINTER; return false; } case MotionEvent.ACTION_CANCEL: return false; } return true; } /** * Classes that wish to be notified when the swipe gesture correctly * triggers a refresh should implement this interface. */ public interface OnRefreshListener { public void onRefresh(); }}
0 0
- SwipeRefreshLayout源码分析
- SwipeRefreshLayout源码分析
- SwipeRefreshLayout 源码
- SwipeRefreshLayout与ViewPager滑动事件冲突源码分析及解决办法
- Android SwipeRefreshLayout下拉刷新控件源码简单分析
- SwipeRefreshLayout源码分析+自定义UC头条下拉刷新Demo
- 下拉刷新SwipeRefreshLayout源码
- SwipeRefreshLayout源码解析
- SwipeRefreshLayout 源码关键方法解析
- SwipeRefreshLayout
- SwipeRefreshLayout
- swiperefreshlayout
- SwipeRefreshLayout
- SwipeRefreshLayout
- SwipeRefreshLayout
- swipeRefreshLayout
- SwipeRefreshLayout
- SwipeRefreshLayout
- LeetCode083 Remove Duplicates from Sorted List
- linux下的find文件查找命令与grep文件内容查找命令
- ASP.NET中进行消息处理(MSMQ) 三
- Java三大核心集合类详解
- IDEA Artifacts配置说明
- SwipeRefreshLayout源码分析
- Java中的private、protected、public和default的区别
- html+css设置百度登陆框day6[作业]
- mac下控制隐藏文件夹显示与隐藏
- 12545
- LeetCode084 Largest Rectangle in Histogram
- Java并发包中Lock的实现原理
- Maven下载Jar包同时下载源文件和文档
- BZOJ 2199: [Usaco2011 Jan]奶牛议会 2-sat