• SwipeRefreshLayout完美添加及完善上拉加载功能


    项目地址:https://git.oschina.net/whos/SwipeRefreshAndLoadLayout/wikis/home

    关于Google推出的下拉刷新控件SwipeRefreshLayout的相关使用方法,大家可以去参考http://blog.csdn.net/geeklei/article/details/38876981,本文也借鉴了其中的一些内容和“颜路的博客”中《官方下拉刷新SwipeRefreshLayout增加上拉加载更多》一文。

    话不多说,直接先上改造效果图(截屏时卡,凑合看吧):

    下拉刷新和上拉加载

         

    简单讲下原始代码的原理:

    下拉时,计算手指移动距离,如果超过一个系统默认的临界值mTouchSlop,该事件就不下发到子控件进行处理,而是SwipeRefreshLayout自己处理。

    变量mDistanceToTriggerSync指定了下拉刷新的临界值,如果下拉距离没有大于该值,则计算下拉距离和mDistanceToTriggerSync的比值,并用该值作为进度百分比对进度条mProgressBar进行设置,同时移动子控件(ListView之类)的位置,屏幕上可以看到进度条颜色缓慢拉长的动画,同时子控件向下移动。

    如果下拉距离大于mDistanceToTriggerSync,则设置动画把子控件位置复位,然后启动下拉刷新的色条循环动画,并执行下拉刷新的监听事件。

    关于进度条SwipeProgressBar的动画显示,Google的代码里埋藏了一个坑人的陷阱。现象就是如果你在底部加了进度条,动画效果异常,不会出现渐变的色条,只是生硬的转换。上面参考的文章里也碰到了这个问题。其实原因很简单,看下图:

    把进度条SwipeProgressBar的高度设置大了后,可以看出其动画效果是在进度条的中心向外部循环画圆,每个循环中圆的颜色不同。重点是圆心的位置。

    看SwipeProgressBar的如下代码,会发现在计算圆心高度cy的时候,取值是进度条高度的一半,这样的话圆心会一直在上面,底部进度条自然动画异常

    [java] view plaincopy
     
    1. void draw(Canvas canvas) {  
    2.     final int width = mBounds.width();  
    3.     final int height = mBounds.height();  
    4.     final int cx = width / 2;  
    5.     final int cy = height / 2;  
    6.     boolean drawTriggerWhileFinishing = false;  
    7.     int restoreCount = canvas.save();  
    8.     canvas.clipRect(mBounds);  

    修改SwipeProgressBar的代码,使其圆心在所在进度条的中心:
    [java] view plaincopy
     
    1.     void draw(Canvas canvas) {  
    2.         final int width = mBounds.width();  
    3.         final int height = mBounds.height();  
    4.         final int cx = width / 2;  
    5. //        final int cy = height / 2;  
    6.         final int cy = mBounds.bottom - height / 2;  
    7.         boolean drawTriggerWhileFinishing = false;  
    8.         int restoreCount = canvas.save();  
    9.         canvas.clipRect(mBounds);  

    效果如图:

    明白了原始代码的原理,就好入手进行修改了,修改的代码会在后面贴出来,注释很详细,这里就不具体分析了。对SDK<14的滑动部分暂时没有进行处理,直接返回了false,待后续改进。已改进

    下面看修改后的功能:

    1.可设置是否打开下拉刷新功能,可设置是否打开上拉加载功能,默认全部打开。

    2.可设置是否在数据不满一屏的情况下打开上拉加载功能,默认关闭。

    3.可单独设置上下进度条的颜色,也可同时设置一样的颜色。

    啰嗦了这么多,上代码:

    SwipeProgressBar:

    [java] view plaincopy
     
    1. package com.dahuo.learn.swiperefreshandload.view;  
    2.   
    3. /* 
    4.  * Copyright (C) 2013 The Android Open Source Project 
    5.  * 
    6.  * Licensed under the Apache License, Version 2.0 (the "License"); 
    7.  * you may not use this file except in compliance with the License. 
    8.  * You may obtain a copy of the License at 
    9.  * 
    10.  *      http://www.apache.org/licenses/LICENSE-2.0 
    11.  * 
    12.  * Unless required by applicable law or agreed to in writing, software 
    13.  * distributed under the License is distributed on an "AS IS" BASIS, 
    14.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    15.  * See the License for the specific language governing permissions and 
    16.  * limitations under the License. 
    17.  */  
    18.   
    19. import android.graphics.Canvas;  
    20. import android.graphics.Paint;  
    21. import android.graphics.Rect;  
    22. import android.graphics.RectF;  
    23. import android.support.v4.view.ViewCompat;  
    24. import android.view.View;  
    25. import android.view.animation.AnimationUtils;  
    26. import android.view.animation.Interpolator;  
    27.   
    28.   
    29. /** 
    30.  * Custom progress bar that shows a cycle of colors as widening circles that 
    31.  * overdraw each other. When finished, the bar is cleared from the inside out as 
    32.  * the main cycle continues. Before running, this can also indicate how close 
    33.  * the user is to triggering something (e.g. how far they need to pull down to 
    34.  * trigger a refresh). 
    35.  */  
    36. final class SwipeProgressBar {  
    37.   
    38.     // Default progress animation colors are grays.  
    39.     private final static int COLOR1 = 0xB3000000;  
    40.     private final static int COLOR2 = 0x80000000;  
    41.     private final static int COLOR3 = 0x4d000000;  
    42.     private final static int COLOR4 = 0x1a000000;  
    43.   
    44.     // The duration of the animation cycle.  
    45.     private static final int ANIMATION_DURATION_MS = 2000;  
    46.   
    47.     // The duration of the animation to clear the bar.  
    48.     private static final int FINISH_ANIMATION_DURATION_MS = 1000;  
    49.   
    50.     // Interpolator for varying the speed of the animation.  
    51.     private static final Interpolator INTERPOLATOR = BakedBezierInterpolator.getInstance();  
    52.   
    53.     private final Paint mPaint = new Paint();  
    54.     private final RectF mClipRect = new RectF();  
    55.     private float mTriggerPercentage;  
    56.     private long mStartTime;  
    57.     private long mFinishTime;  
    58.     private boolean mRunning;  
    59.   
    60.     // Colors used when rendering the animation,  
    61.     private int mColor1;  
    62.     private int mColor2;  
    63.     private int mColor3;  
    64.     private int mColor4;  
    65.     private View mParent;  
    66.   
    67.     private Rect mBounds = new Rect();  
    68.   
    69.     public SwipeProgressBar(View parent) {  
    70.         mParent = parent;  
    71.         mColor1 = COLOR1;  
    72.         mColor2 = COLOR2;  
    73.         mColor3 = COLOR3;  
    74.         mColor4 = COLOR4;  
    75.     }  
    76.   
    77.     /** 
    78.      * Set the four colors used in the progress animation. The first color will 
    79.      * also be the color of the bar that grows in response to a user swipe 
    80.      * gesture. 
    81.      * 
    82.      * @param color1 Integer representation of a color. 
    83.      * @param color2 Integer representation of a color. 
    84.      * @param color3 Integer representation of a color. 
    85.      * @param color4 Integer representation of a color. 
    86.      */  
    87.     void setColorScheme(int color1, int color2, int color3, int color4) {  
    88.         mColor1 = color1;  
    89.         mColor2 = color2;  
    90.         mColor3 = color3;  
    91.         mColor4 = color4;  
    92.     }  
    93.   
    94.     /** 
    95.      * Update the progress the user has made toward triggering the swipe 
    96.      * gesture. and use this value to update the percentage of the trigger that 
    97.      * is shown. 
    98.      */  
    99.     void setTriggerPercentage(float triggerPercentage) {  
    100.         mTriggerPercentage = triggerPercentage;  
    101.         mStartTime = 0;  
    102.         ViewCompat.postInvalidateOnAnimation(mParent);  
    103.     }  
    104.   
    105.     /** 
    106.      * Start showing the progress animation. 
    107.      */  
    108.     void start() {  
    109.         if (!mRunning) {  
    110.             mTriggerPercentage = 0;  
    111.             mStartTime = AnimationUtils.currentAnimationTimeMillis();  
    112.             mRunning = true;  
    113.             mParent.postInvalidate();  
    114.         }  
    115.     }  
    116.   
    117.     /** 
    118.      * Stop showing the progress animation. 
    119.      */  
    120.     void stop() {  
    121.         if (mRunning) {  
    122.             mTriggerPercentage = 0;  
    123.             mFinishTime = AnimationUtils.currentAnimationTimeMillis();  
    124.             mRunning = false;  
    125.             mParent.postInvalidate();  
    126.         }  
    127.     }  
    128.   
    129.     /** 
    130.      * @return Return whether the progress animation is currently running. 
    131.      */  
    132.     boolean isRunning() {  
    133.         return mRunning || mFinishTime > 0;  
    134.     }  
    135.   
    136.     void draw(Canvas canvas) {  
    137.         final int width = mBounds.width();  
    138.         final int height = mBounds.height();  
    139.         final int cx = width / 2;  
    140. //        final int cy = height / 2;  
    141.         final int cy = mBounds.bottom - height / 2;  
    142.         boolean drawTriggerWhileFinishing = false;  
    143.         int restoreCount = canvas.save();  
    144.         canvas.clipRect(mBounds);  
    145.   
    146.         if (mRunning || (mFinishTime > 0)) {  
    147.             long now = AnimationUtils.currentAnimationTimeMillis();  
    148.             long elapsed = (now - mStartTime) % ANIMATION_DURATION_MS;  
    149.             long iterations = (now - mStartTime) / ANIMATION_DURATION_MS;  
    150.             float rawProgress = (elapsed / (ANIMATION_DURATION_MS / 100f));  
    151.   
    152.             // If we're not running anymore, that means we're running through  
    153.             // the finish animation.  
    154.             if (!mRunning) {  
    155.                 // If the finish animation is done, don't draw anything, and  
    156.                 // don't repost.  
    157.                 if ((now - mFinishTime) >= FINISH_ANIMATION_DURATION_MS) {  
    158.                     mFinishTime = 0;  
    159.                     return;  
    160.                 }  
    161.   
    162.                 // Otherwise, use a 0 opacity alpha layer to clear the animation  
    163.                 // from the inside out. This layer will prevent the circles from  
    164.                 // drawing within its bounds.  
    165.                 long finishElapsed = (now - mFinishTime) % FINISH_ANIMATION_DURATION_MS;  
    166.                 float finishProgress = (finishElapsed / (FINISH_ANIMATION_DURATION_MS / 100f));  
    167.                 float pct = (finishProgress / 100f);  
    168.                 // Radius of the circle is half of the screen.  
    169.                 float clearRadius = width / 2 * INTERPOLATOR.getInterpolation(pct);  
    170.                 mClipRect.set(cx - clearRadius, 0, cx + clearRadius, height);  
    171.                 canvas.saveLayerAlpha(mClipRect, 0, 0);  
    172.                 // Only draw the trigger if there is a space in the center of  
    173.                 // this refreshing view that needs to be filled in by the  
    174.                 // trigger. If the progress view is just still animating, let it  
    175.                 // continue animating.  
    176.                 drawTriggerWhileFinishing = true;  
    177.             }  
    178.   
    179.             // First fill in with the last color that would have finished drawing.  
    180.             if (iterations == 0) {  
    181.                 canvas.drawColor(mColor1);  
    182.             } else {  
    183.                 if (rawProgress >= 0 && rawProgress < 25) {  
    184.                     canvas.drawColor(mColor4);  
    185.                 } else if (rawProgress >= 25 && rawProgress < 50) {  
    186.                     canvas.drawColor(mColor1);  
    187.                 } else if (rawProgress >= 50 && rawProgress < 75) {  
    188.                     canvas.drawColor(mColor2);  
    189.                 } else {  
    190.                     canvas.drawColor(mColor3);  
    191.                 }  
    192.             }  
    193.   
    194.             // Then draw up to 4 overlapping concentric circles of varying radii, based on how far  
    195.             // along we are in the cycle.  
    196.             // progress 0-50 draw mColor2  
    197.             // progress 25-75 draw mColor3  
    198.             // progress 50-100 draw mColor4  
    199.             // progress 75 (wrap to 25) draw mColor1  
    200.             if ((rawProgress >= 0 && rawProgress <= 25)) {  
    201.                 float pct = (((rawProgress + 25) * 2) / 100f);  
    202.                 drawCircle(canvas, cx, cy, mColor1, pct);  
    203.             }  
    204.             if (rawProgress >= 0 && rawProgress <= 50) {  
    205.                 float pct = ((rawProgress * 2) / 100f);  
    206.                 drawCircle(canvas, cx, cy, mColor2, pct);  
    207.             }  
    208.             if (rawProgress >= 25 && rawProgress <= 75) {  
    209.                 float pct = (((rawProgress - 25) * 2) / 100f);  
    210.                 drawCircle(canvas, cx, cy, mColor3, pct);  
    211.             }  
    212.             if (rawProgress >= 50 && rawProgress <= 100) {  
    213.                 float pct = (((rawProgress - 50) * 2) / 100f);  
    214.                 drawCircle(canvas, cx, cy, mColor4, pct);  
    215.             }  
    216.             if ((rawProgress >= 75 && rawProgress <= 100)) {  
    217.                 float pct = (((rawProgress - 75) * 2) / 100f);  
    218.                 drawCircle(canvas, cx, cy, mColor1, pct);  
    219.             }  
    220.             if (mTriggerPercentage > 0 && drawTriggerWhileFinishing) {  
    221.                 // There is some portion of trigger to draw. Restore the canvas,  
    222.                 // then draw the trigger. Otherwise, the trigger does not appear  
    223.                 // until after the bar has finished animating and appears to  
    224.                 // just jump in at a larger width than expected.  
    225.                 canvas.restoreToCount(restoreCount);  
    226.                 restoreCount = canvas.save();  
    227.                 canvas.clipRect(mBounds);  
    228.                 drawTrigger(canvas, cx, cy);  
    229.             }  
    230.             // Keep running until we finish out the last cycle.  
    231.             ViewCompat.postInvalidateOnAnimation(mParent);  
    232.         } else {  
    233.             // Otherwise if we're in the middle of a trigger, draw that.  
    234.             if (mTriggerPercentage > 0 && mTriggerPercentage <= 1.0) {  
    235.                 drawTrigger(canvas, cx, cy);  
    236.             }  
    237.         }  
    238.         canvas.restoreToCount(restoreCount);  
    239.     }  
    240.   
    241.     private void drawTrigger(Canvas canvas, int cx, int cy) {  
    242.         mPaint.setColor(mColor1);  
    243.         canvas.drawCircle(cx, cy, cx * mTriggerPercentage, mPaint);  
    244.     }  
    245.   
    246.     /** 
    247.      * Draws a circle centered in the view. 
    248.      * 
    249.      * @param canvas the canvas to draw on 
    250.      * @param cx the center x coordinate 
    251.      * @param cy the center y coordinate 
    252.      * @param color the color to draw 
    253.      * @param pct the percentage of the view that the circle should cover 
    254.      */  
    255.     private void drawCircle(Canvas canvas, float cx, float cy, int color, float pct) {  
    256.         mPaint.setColor(color);  
    257.         canvas.save();  
    258.         canvas.translate(cx, cy);  
    259.         float radiusScale = INTERPOLATOR.getInterpolation(pct);  
    260.         canvas.scale(radiusScale, radiusScale);  
    261.         canvas.drawCircle(0, 0, cx, mPaint);  
    262.         canvas.restore();  
    263.     }  
    264.   
    265.     /** 
    266.      * Set the drawing bounds of this SwipeProgressBar. 
    267.      */  
    268.     void setBounds(int left, int top, int right, int bottom) {  
    269.         mBounds.left = left;  
    270.         mBounds.top = top;  
    271.         mBounds.right = right;  
    272.         mBounds.bottom = bottom;  
    273.     }  
    274. }  

    SwipeRefreshLayout:
    [java] view plaincopy
     
    1. /* 
    2.  * Copyright (C) 2013 The Android Open Source Project 
    3.  * 
    4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
    5.  * you may not use this file except in compliance with the License. 
    6.  * You may obtain a copy of the License at 
    7.  * 
    8.  *      http://www.apache.org/licenses/LICENSE-2.0 
    9.  * 
    10.  * Unless required by applicable law or agreed to in writing, software 
    11.  * distributed under the License is distributed on an "AS IS" BASIS, 
    12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    13.  * See the License for the specific language governing permissions and 
    14.  * limitations under the License. 
    15.  */  
    16.   
    17. package com.dahuo.learn.swiperefreshandload.view;  
    18.   
    19. import android.content.Context;  
    20. import android.content.res.Resources;  
    21. import android.content.res.TypedArray;  
    22. import android.graphics.Canvas;  
    23. import android.support.v4.view.MotionEventCompat;  
    24. import android.support.v4.view.ViewCompat;  
    25. import android.util.AttributeSet;  
    26. import android.util.DisplayMetrics;  
    27. import android.util.Log;  
    28. import android.view.MotionEvent;  
    29. import android.view.View;  
    30. import android.view.ViewConfiguration;  
    31. import android.view.ViewGroup;  
    32. import android.view.animation.AccelerateInterpolator;  
    33. import android.view.animation.Animation;  
    34. import android.view.animation.Animation.AnimationListener;  
    35. import android.view.animation.DecelerateInterpolator;  
    36. import android.view.animation.Transformation;  
    37. import android.widget.AbsListView;  
    38.   
    39.   
    40. /** 
    41.  * The SwipeRefreshLayout should be used whenever the user can refresh the 
    42.  * contents of a view via a vertical swipe gesture. The activity that 
    43.  * instantiates this view should add an OnRefreshListener to be notified 
    44.  * whenever the swipe to refresh gesture is completed. The SwipeRefreshLayout 
    45.  * will notify the listener each and every time the gesture is completed again; 
    46.  * the listener is responsible for correctly determining when to actually 
    47.  * initiate a refresh of its content. If the listener determines there should 
    48.  * not be a refresh, it must call setRefreshing(false) to cancel any visual 
    49.  * indication of a refresh. If an activity wishes to show just the progress 
    50.  * animation, it should call setRefreshing(true). To disable the gesture and progress 
    51.  * animation, call setEnabled(false) on the view. 
    52.  * 
    53.  * <p> This layout should be made the parent of the view that will be refreshed as a 
    54.  * result of the gesture and can only support one direct child. This view will 
    55.  * also be made the target of the gesture and will be forced to match both the 
    56.  * width and the height supplied in this layout. The SwipeRefreshLayout does not 
    57.  * provide accessibility events; instead, a menu item must be provided to allow 
    58.  * refresh of the content wherever this gesture is used.</p> 
    59.  */  
    60. public class SwipeRefreshLayout extends ViewGroup {  
    61.     private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName();  
    62.   
    63.     private static final long RETURN_TO_ORIGINAL_POSITION_TIMEOUT = 300;  
    64.     private static final float ACCELERATE_INTERPOLATION_FACTOR = 1.5f;  
    65.     private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;  
    66.     private static final float PROGRESS_BAR_HEIGHT = 4;  
    67.     private static final float MAX_SWIPE_DISTANCE_FACTOR = .6f;  
    68.     private static final int REFRESH_TRIGGER_DISTANCE = 120;  
    69.     private static final int INVALID_POINTER = -1;  
    70.   
    71.     private SwipeProgressBar mProgressBar; //the thing that shows progress is going  
    72.     private SwipeProgressBar mProgressBarBottom;  
    73.     private View mTarget; //the content that gets pulled down  
    74.     private int mOriginalOffsetTop;  
    75.     private OnRefreshListener mRefreshListener;  
    76.     private OnLoadListener mLoadListener;  
    77.     private int mFrom;  
    78.     private boolean mRefreshing = false;  
    79.     private boolean mLoading = false;  
    80.     private int mTouchSlop;  
    81.     private float mDistanceToTriggerSync = -1;  
    82.     private int mMediumAnimationDuration;  
    83.     private float mFromPercentage = 0;  
    84.     private float mCurrPercentage = 0;  
    85.     private int mProgressBarHeight;  
    86.     private int mCurrentTargetOffsetTop;  
    87.   
    88.     private float mInitialMotionY;  
    89.     private float mLastMotionY;  
    90.     private boolean mIsBeingDragged;  
    91.     private int mActivePointerId = INVALID_POINTER;  
    92.   
    93.     // Target is returning to its start offset because it was cancelled or a  
    94.     // refresh was triggered.  
    95.     private boolean mReturningToStart;  
    96.     private final DecelerateInterpolator mDecelerateInterpolator;  
    97.     private final AccelerateInterpolator mAccelerateInterpolator;  
    98.     private static final int[] LAYOUT_ATTRS = new int[] {  
    99.         android.R.attr.enabled  
    100.     };  
    101.     private Mode mMode = Mode.getDefault();  
    102.     //之前手势的方向,为了解决同一个触点前后移动方向不同导致后一个方向会刷新的问题,  
    103.     //这里Mode.DISABLED无意义,只是一个初始值,和上拉/下拉方向进行区分  
    104.     private Mode mLastDirection = Mode.DISABLED;  
    105.     private int mDirection = 0;  
    106.     //当子控件移动到尽头时才开始计算初始点的位置  
    107.     private float mStartPoint;  
    108.     private boolean up;  
    109.     private boolean down;  
    110.     //数据不足一屏时是否打开上拉加载模式  
    111.     private boolean loadNoFull = false;  
    112.   
    113.     //对下拉或上拉进行复位  
    114.     private final Animation mAnimateToStartPosition = new Animation() {  
    115.         @Override  
    116.         public void applyTransformation(float interpolatedTime, Transformation t) {  
    117.             int targetTop = 0;  
    118.             if (mFrom != mOriginalOffsetTop) {  
    119.                 targetTop = (mFrom + (int)((mOriginalOffsetTop - mFrom) * interpolatedTime));  
    120.             }  
    121.             int offset = targetTop - mTarget.getTop();  
    122.             //注释掉这里,不然上拉后回复原位置会很快,不平滑  
    123. //            final int currentTop = mTarget.getTop();  
    124. //            if (offset + currentTop < 0) {  
    125. //                offset = 0 - currentTop;  
    126. //            }  
    127.             setTargetOffsetTopAndBottom(offset);  
    128.         }  
    129.     };  
    130.   
    131.     //设置上方进度条的完成度百分比  
    132.     private Animation mShrinkTrigger = new Animation() {  
    133.         @Override  
    134.         public void applyTransformation(float interpolatedTime, Transformation t) {  
    135.             float percent = mFromPercentage + ((0 - mFromPercentage) * interpolatedTime);  
    136.             mProgressBar.setTriggerPercentage(percent);  
    137.         }  
    138.     };  
    139.   
    140.     //设置下方进度条的完成度百分比  
    141.     private Animation mShrinkTriggerBottom = new Animation() {  
    142.         @Override  
    143.         public void applyTransformation(float interpolatedTime, Transformation t) {  
    144.             float percent = mFromPercentage + ((0 - mFromPercentage) * interpolatedTime);  
    145.             mProgressBarBottom.setTriggerPercentage(percent);  
    146.         }  
    147.     };  
    148.   
    149.     //监听,回复初始位置  
    150.     private final AnimationListener mReturnToStartPositionListener = new BaseAnimationListener() {  
    151.         @Override  
    152.         public void onAnimationEnd(Animation animation) {  
    153.             // Once the target content has returned to its start position, reset  
    154.             // the target offset to 0  
    155.             mCurrentTargetOffsetTop = 0;  
    156.             mLastDirection = Mode.DISABLED;  
    157.         }  
    158.     };  
    159.   
    160.     //回复进度条百分比  
    161.     private final AnimationListener mShrinkAnimationListener = new BaseAnimationListener() {  
    162.         @Override  
    163.         public void onAnimationEnd(Animation animation) {  
    164.             mCurrPercentage = 0;  
    165.         }  
    166.     };  
    167.   
    168.     //回复初始位置  
    169.     private final Runnable mReturnToStartPosition = new Runnable() {  
    170.   
    171.         @Override  
    172.         public void run() {  
    173.             mReturningToStart = true;  
    174.             animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(),  
    175.                     mReturnToStartPositionListener);  
    176.         }  
    177.   
    178.     };  
    179.   
    180.     // Cancel the refresh gesture and animate everything back to its original state.  
    181.     private final Runnable mCancel = new Runnable() {  
    182.   
    183.         @Override  
    184.         public void run() {  
    185.             mReturningToStart = true;  
    186.             // Timeout fired since the user last moved their finger; animate the  
    187.             // trigger to 0 and put the target back at its original position  
    188.             if (mProgressBar != null || mProgressBarBottom != null) {  
    189.                 mFromPercentage = mCurrPercentage;  
    190.                 if(mDirection > 0 && ((mMode == Mode.PULL_FROM_START) || (mMode == Mode.BOTH)))  
    191.                 {  
    192.                     mShrinkTrigger.setDuration(mMediumAnimationDuration);  
    193.                     mShrinkTrigger.setAnimationListener(mShrinkAnimationListener);  
    194.                     mShrinkTrigger.reset();  
    195.                     mShrinkTrigger.setInterpolator(mDecelerateInterpolator);  
    196.                     startAnimation(mShrinkTrigger);  
    197.                 }  
    198.                 else if(mDirection < 0 && ((mMode == Mode.PULL_FROM_END) || (mMode == Mode.BOTH)))  
    199.                 {  
    200.                     mShrinkTriggerBottom.setDuration(mMediumAnimationDuration);  
    201.                     mShrinkTriggerBottom.setAnimationListener(mShrinkAnimationListener);  
    202.                     mShrinkTriggerBottom.reset();  
    203.                     mShrinkTriggerBottom.setInterpolator(mDecelerateInterpolator);  
    204.                     startAnimation(mShrinkTriggerBottom);                     
    205.                 }  
    206.             }  
    207.             mDirection = 0;  
    208.             animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(),  
    209.                     mReturnToStartPositionListener);  
    210.         }  
    211.   
    212.     };  
    213.   
    214.     /** 
    215.      * Simple constructor to use when creating a SwipeRefreshLayout from code. 
    216.      * @param context 
    217.      */  
    218.     public SwipeRefreshLayout(Context context) {  
    219.         this(context, null);  
    220.     }  
    221.   
    222.     /** 
    223.      * Constructor that is called when inflating SwipeRefreshLayout from XML. 
    224.      * @param context 
    225.      * @param attrs 
    226.      */  
    227.     public SwipeRefreshLayout(Context context, AttributeSet attrs) {  
    228.         super(context, attrs);  
    229.   
    230.         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();  
    231.   
    232.         mMediumAnimationDuration = getResources().getInteger(  
    233.                 android.R.integer.config_mediumAnimTime);  
    234.   
    235.         setWillNotDraw(false);  
    236.         mProgressBar = new SwipeProgressBar(this);  
    237.         mProgressBarBottom = new SwipeProgressBar(this);  
    238.         final DisplayMetrics metrics = getResources().getDisplayMetrics();  
    239.         mProgressBarHeight = (int) (metrics.density * PROGRESS_BAR_HEIGHT);  
    240.         mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);  
    241.         mAccelerateInterpolator = new AccelerateInterpolator(ACCELERATE_INTERPOLATION_FACTOR);  
    242.   
    243.         final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);  
    244.         setEnabled(a.getBoolean(0, true));  
    245.         a.recycle();  
    246.     }  
    247.   
    248.     @Override  
    249.     public void onAttachedToWindow() {  
    250.         super.onAttachedToWindow();  
    251.         removeCallbacks(mCancel);  
    252.         removeCallbacks(mReturnToStartPosition);  
    253.     }  
    254.   
    255.     @Override  
    256.     public void onDetachedFromWindow() {  
    257.         super.onDetachedFromWindow();  
    258.         removeCallbacks(mReturnToStartPosition);  
    259.         removeCallbacks(mCancel);  
    260.     }  
    261.   
    262.     //对子控件进行移动  
    263.     private void animateOffsetToStartPosition(int from, AnimationListener listener) {  
    264.         mFrom = from;  
    265.         mAnimateToStartPosition.reset();  
    266.         mAnimateToStartPosition.setDuration(mMediumAnimationDuration);  
    267.         mAnimateToStartPosition.setAnimationListener(listener);  
    268.         mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);  
    269.         mTarget.startAnimation(mAnimateToStartPosition);  
    270.     }  
    271.   
    272.     /** 
    273.      * Set the listener to be notified when a refresh is triggered via the swipe 
    274.      * gesture. 
    275.      */  
    276.     public void setOnRefreshListener(OnRefreshListener listener) {  
    277.         mRefreshListener = listener;  
    278.     }  
    279.   
    280.     public void setOnLoadListener(OnLoadListener listener) {  
    281.         mLoadListener = listener;  
    282.     }  
    283.   
    284.     //设置进度条的显示百分比  
    285.     private void setTriggerPercentage(float percent) {  
    286.         if (percent == 0f) {  
    287.             // No-op. A null trigger means it's uninitialized, and setting it to zero-percent  
    288.             // means we're trying to reset state, so there's nothing to reset in this case.  
    289.             mCurrPercentage = 0;  
    290.             return;  
    291.         }  
    292.         mCurrPercentage = percent;  
    293.         if (((mMode == Mode.PULL_FROM_START) || (mMode == Mode.BOTH))  
    294.                 && mLastDirection != Mode.PULL_FROM_END && !mLoading)  
    295.         {  
    296.             mProgressBar.setTriggerPercentage(percent);   
    297.         }  
    298.         else if(((mMode == Mode.PULL_FROM_END) || (mMode == Mode.BOTH))  
    299.                 && mLastDirection != Mode.PULL_FROM_START && !mRefreshing)  
    300.         {  
    301.             mProgressBarBottom.setTriggerPercentage(percent);  
    302.         }  
    303.     }  
    304.   
    305.     /** 
    306.      * Notify the widget that refresh state has changed. Do not call this when 
    307.      * refresh is triggered by a swipe gesture. 
    308.      * 
    309.      * @param refreshing Whether or not the view should show refresh progress. 
    310.      */  
    311.     public void setRefreshing(boolean refreshing) {  
    312.         if (mRefreshing != refreshing) {  
    313.             ensureTarget();  
    314.             mCurrPercentage = 0;  
    315.             mRefreshing = refreshing;  
    316.             if (mRefreshing) {  
    317.                 mProgressBar.start();  
    318.             } else {  
    319.                 mLastDirection = Mode.DISABLED;  
    320.                 mProgressBar.stop();  
    321.             }  
    322.         }  
    323.     }  
    324.   
    325.     public void setLoading(boolean loading) {  
    326.         if (mLoading != loading) {  
    327.             ensureTarget();  
    328.             mCurrPercentage = 0;  
    329.             mLoading = loading;  
    330.             if (mLoading) {  
    331.                 mProgressBarBottom.start();  
    332.             } else {  
    333.                 mLastDirection = Mode.DISABLED;  
    334.                 mProgressBarBottom.stop();  
    335.             }  
    336.         }  
    337.     }  
    338.   
    339.     /** 
    340.      * @deprecated Use {@link #setColorSchemeResources(int, int, int, int)} 
    341.      */  
    342.     @Deprecated  
    343.     private void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) {  
    344.         setColorSchemeResources(colorRes1, colorRes2, colorRes3, colorRes4);  
    345.     }  
    346.   
    347.     /** 
    348.      * Set the four colors used in the progress animation from color resources. 
    349.      * The first color will also be the color of the bar that grows in response 
    350.      * to a user swipe gesture. 
    351.      */  
    352.     public void setTopColor(int colorRes1, int colorRes2, int colorRes3,  
    353.             int colorRes4)  
    354.     {  
    355.         setColorSchemeResources(colorRes1, colorRes2, colorRes3, colorRes4);  
    356.     }  
    357.       
    358.     public void setBottomColor(int colorRes1, int colorRes2, int colorRes3,  
    359.             int colorRes4)  
    360.     {  
    361.         setColorSchemeResourcesBottom(colorRes1, colorRes2, colorRes3, colorRes4);  
    362.     }  
    363.   
    364.     public void setColor(int colorRes1, int colorRes2, int colorRes3,  
    365.             int colorRes4){  
    366.         setColorSchemeResources(colorRes1, colorRes2, colorRes3, colorRes4);  
    367.         setColorSchemeResourcesBottom(colorRes1, colorRes2, colorRes3, colorRes4);  
    368.     }  
    369.       
    370.     private void setColorSchemeResources(int colorRes1, int colorRes2, int colorRes3,  
    371.             int colorRes4) {  
    372.         final Resources res = getResources();  
    373.         setColorSchemeColors(res.getColor(colorRes1), res.getColor(colorRes2),  
    374.                 res.getColor(colorRes3), res.getColor(colorRes4));  
    375.     }  
    376.   
    377.     private void setColorSchemeResourcesBottom(int colorRes1, int colorRes2, int colorRes3,  
    378.             int colorRes4) {  
    379.         final Resources res = getResources();  
    380.         setColorSchemeColorsBottom(res.getColor(colorRes1), res.getColor(colorRes2),  
    381.                 res.getColor(colorRes3), res.getColor(colorRes4));  
    382.     }  
    383.   
    384.     /** 
    385.      * Set the four colors used in the progress animation. The first color will 
    386.      * also be the color of the bar that grows in response to a user swipe 
    387.      * gesture. 
    388.      */  
    389.     private void setColorSchemeColors(int color1, int color2, int color3, int color4) {  
    390.         ensureTarget();  
    391.         mProgressBar.setColorScheme(color1, color2, color3, color4);  
    392.     }  
    393.   
    394.     private void setColorSchemeColorsBottom(int color1, int color2, int color3, int color4) {  
    395.         ensureTarget();  
    396.         mProgressBarBottom.setColorScheme(color1, color2, color3, color4);  
    397.     }  
    398.     /** 
    399.      * @return Whether the SwipeRefreshWidget is actively showing refresh 
    400.      *         progress. 
    401.      */  
    402.     public boolean isRefreshing() {  
    403.         return mRefreshing;  
    404.     }  
    405.   
    406.     public boolean isLoading() {  
    407.         return mLoading;  
    408.     }  
    409.   
    410.     private void ensureTarget() {  
    411.         // Don't bother getting the parent height if the parent hasn't been laid out yet.  
    412.         if (mTarget == null) {  
    413.             if (getChildCount() > 1 && !isInEditMode()) {  
    414.                 throw new IllegalStateException(  
    415.                         "SwipeRefreshLayout can host only one direct child");  
    416.             }  
    417.             mTarget = getChildAt(0);  
    418.             mOriginalOffsetTop = mTarget.getTop() + getPaddingTop();  
    419.         }  
    420.         if (mDistanceToTriggerSync == -1) {  
    421.             if (getParent() != null && ((View)getParent()).getHeight() > 0) {  
    422.                 final DisplayMetrics metrics = getResources().getDisplayMetrics();  
    423.                 mDistanceToTriggerSync = (int) Math.min(  
    424.                         ((View) getParent()) .getHeight() * MAX_SWIPE_DISTANCE_FACTOR,  
    425.                                 REFRESH_TRIGGER_DISTANCE * metrics.density);  
    426.             }  
    427.         }  
    428.     }  
    429.   
    430.     @Override  
    431.     public void draw(Canvas canvas) {  
    432.         super.draw(canvas);  
    433.         mProgressBar.draw(canvas);  
    434.         mProgressBarBottom.draw(canvas);  
    435.     }  
    436.   
    437.     @Override  
    438.     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
    439.         final int width =  getMeasuredWidth();  
    440.         final int height = getMeasuredHeight();  
    441.         mProgressBar.setBounds(0, 0, width, mProgressBarHeight);  
    442.         if (getChildCount() == 0) {  
    443.             return;  
    444.         }  
    445.         final View child = getChildAt(0);  
    446.         final int childLeft = getPaddingLeft();  
    447.         final int childTop = mCurrentTargetOffsetTop + getPaddingTop();  
    448.         final int childWidth = width - getPaddingLeft() - getPaddingRight();  
    449.         final int childHeight = height - getPaddingTop() - getPaddingBottom();  
    450.         child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);  
    451.         mProgressBarBottom.setBounds(0, height-mProgressBarHeight, width, height);  
    452.     }  
    453.   
    454.     @Override  
    455.     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    456.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
    457.         if (getChildCount() > 1 && !isInEditMode()) {  
    458.             throw new IllegalStateException("SwipeRefreshLayout can host only one direct child");  
    459.         }  
    460.         if (getChildCount() > 0) {  
    461.             getChildAt(0).measure(  
    462.                     MeasureSpec.makeMeasureSpec(  
    463.                             getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),  
    464.                             MeasureSpec.EXACTLY),  
    465.                     MeasureSpec.makeMeasureSpec(  
    466.                             getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),  
    467.                             MeasureSpec.EXACTLY));  
    468.         }  
    469.     }  
    470.   
    471.     /** 
    472.      * @return Whether it is possible for the child view of this layout to 
    473.      *         scroll up. Override this if the child view is a custom view. 
    474.      */  
    475.     public boolean canChildScrollUp() {  
    476.         if (android.os.Build.VERSION.SDK_INT < 14) {  
    477.             if (mTarget instanceof AbsListView) {  
    478.                 final AbsListView absListView = (AbsListView) mTarget;  
    479.                 return absListView.getChildCount() > 0  
    480.                         && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)  
    481.                                 .getTop() < absListView.getPaddingTop());  
    482.             } else {  
    483.                 return mTarget.getScrollY() > 0;  
    484.             }  
    485.         } else {  
    486.             return ViewCompat.canScrollVertically(mTarget, -1);  
    487.         }  
    488.     }  
    489.   
    490.     public boolean canChildScrollDown() {  
    491.         if (android.os.Build.VERSION.SDK_INT < 14) {  
    492.             if (mTarget instanceof AbsListView) {  
    493.                 final AbsListView absListView = (AbsListView) mTarget;  
    494.                 View lastChild = absListView.getChildAt(absListView.getChildCount() - 1);  
    495.                 if (lastChild != null) {  
    496.                     return (absListView.getLastVisiblePosition() == (absListView.getCount() - 1))  
    497.                             && lastChild.getBottom() > absListView.getPaddingBottom();  
    498.                 }  
    499.                 else  
    500.                 {  
    501.                     return false;  
    502.                 }  
    503.             } else {  
    504.                 return mTarget.getHeight() - mTarget.getScrollY() > 0;  
    505.             }  
    506.         } else {  
    507.             return ViewCompat.canScrollVertically(mTarget, 1);  
    508.         }  
    509.     }  
    510.   
    511.     @Override  
    512.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
    513.         ensureTarget();  
    514.   
    515.         final int action = MotionEventCompat.getActionMasked(ev);  
    516.   
    517.         if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {  
    518.             mReturningToStart = false;  
    519.         }  
    520.   
    521.         if (!isEnabled() || mReturningToStart) {  
    522.             // Fail fast if we're not in a state where a swipe is possible  
    523.             return false;  
    524.         }  
    525.   
    526.         switch (action) {  
    527.             case MotionEvent.ACTION_DOWN:  
    528.                 mLastMotionY = mInitialMotionY = ev.getY();  
    529.                 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);  
    530.                 mIsBeingDragged = false;  
    531.                 mCurrPercentage = 0;  
    532.                 mStartPoint = mInitialMotionY;  
    533.   
    534.                 //这里用up/down记录子控件能否下拉,如果当前子控件不能上下滑动,但当手指按下并移动子控件时,控件就会变得可滑动  
    535.                 //后面的一些处理不能直接使用canChildScrollUp/canChildScrollDown  
    536.                 //但仍存在问题:当数据不满一屏且设置可以上拉模式后,多次快速上拉会激发上拉加载  
    537.                 up = canChildScrollUp();  
    538.                 down = canChildScrollDown();                      
    539.                 break;  
    540.   
    541.             case MotionEvent.ACTION_MOVE:  
    542.                 if (mActivePointerId == INVALID_POINTER) {  
    543.                     Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");  
    544.                     return false;  
    545.                 }  
    546.   
    547.                 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);  
    548.                 if (pointerIndex < 0) {  
    549.                     Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");  
    550.                     return false;  
    551.                 }  
    552.   
    553.                 final float y = MotionEventCompat.getY(ev, pointerIndex);  
    554. //                final float yDiff = y - mInitialMotionY;  
    555.                 final float yDiff = y - mStartPoint;  
    556.                 //若上个手势的方向和当前手势方向不一致,返回  
    557.                 if((mLastDirection == Mode.PULL_FROM_START && yDiff < 0) ||  
    558.                         (mLastDirection == Mode.PULL_FROM_END && yDiff > 0))  
    559.                 {  
    560.                     return false;  
    561.                 }  
    562.                 //下拉或上拉时,子控件本身能够滑动时,记录当前手指位置,当其滑动到尽头时,  
    563.                 //mStartPoint作为下拉刷新或上拉加载的手势起点  
    564.                 if ((canChildScrollUp() && yDiff > 0) || (canChildScrollDown() && yDiff < 0))  
    565.                 {  
    566.                     mStartPoint = y;  
    567.                 }  
    568.   
    569.                 //下拉  
    570.                 if (yDiff > mTouchSlop)  
    571.                 {  
    572.                     //若当前子控件能向下滑动,或者上个手势为上拉,则返回  
    573.                     if (canChildScrollUp() || mLastDirection == Mode.PULL_FROM_END)  
    574.                     {  
    575.                         mIsBeingDragged = false;  
    576.                         return false;  
    577.                     }  
    578.                     if ((mMode == Mode.PULL_FROM_START) || (mMode == Mode.BOTH))  
    579.                     {  
    580.                         mLastMotionY = y;  
    581.                         mIsBeingDragged = true;  
    582.                         mLastDirection = Mode.PULL_FROM_START;  
    583.                     }  
    584.                 }  
    585.                 //上拉  
    586.                 else if (-yDiff > mTouchSlop) {  
    587.                     //若当前子控件能向上滑动,或者上个手势为下拉,则返回  
    588.                     if (canChildScrollDown() || mLastDirection == Mode.PULL_FROM_START)  
    589.                     {  
    590.                         mIsBeingDragged = false;  
    591.                         return false;  
    592.                     }  
    593.                     //若子控件不能上下滑动,说明数据不足一屏,若不满屏不加载,返回  
    594.                     if (!up && !down && !loadNoFull)  
    595.                     {  
    596.                             mIsBeingDragged = false;  
    597.                             return false;  
    598.                     }  
    599.                     if ((mMode == Mode.PULL_FROM_END) || (mMode == Mode.BOTH))  
    600.                     {  
    601.                         mLastMotionY = y;  
    602.                         mIsBeingDragged = true;  
    603.                         mLastDirection = Mode.PULL_FROM_END;  
    604.                     }  
    605.                 }  
    606.                 break;  
    607.   
    608.             case MotionEventCompat.ACTION_POINTER_UP:  
    609.                 onSecondaryPointerUp(ev);  
    610.                 break;  
    611.   
    612.             case MotionEvent.ACTION_UP:  
    613.             case MotionEvent.ACTION_CANCEL:  
    614.                 mIsBeingDragged = false;  
    615.                 mCurrPercentage = 0;  
    616.                 mActivePointerId = INVALID_POINTER;  
    617.                 mLastDirection = Mode.DISABLED;  
    618.                 break;  
    619.         }  
    620.   
    621.         return mIsBeingDragged;  
    622.     }  
    623.   
    624.     @Override  
    625.     public void requestDisallowInterceptTouchEvent(boolean b) {  
    626.         // Nope.  
    627.     }  
    628.   
    629.     @Override  
    630.     public boolean onTouchEvent(MotionEvent ev) {  
    631.         final int action = MotionEventCompat.getActionMasked(ev);  
    632.   
    633.         if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {  
    634.             mReturningToStart = false;  
    635.         }  
    636.   
    637.         if (!isEnabled() || mReturningToStart) {  
    638.             // Fail fast if we're not in a state where a swipe is possible  
    639.             return false;  
    640.         }  
    641.   
    642.         switch (action) {  
    643.             case MotionEvent.ACTION_DOWN:  
    644.                 mLastMotionY = mInitialMotionY = ev.getY();  
    645.                 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);  
    646.                 mIsBeingDragged = false;  
    647.                 mCurrPercentage = 0;  
    648.                 mStartPoint = mInitialMotionY;  
    649.   
    650.                 up = canChildScrollUp();  
    651.                 down = canChildScrollDown();                      
    652.                 break;  
    653.   
    654.             case MotionEvent.ACTION_MOVE:  
    655.                 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);  
    656.                 if (pointerIndex < 0) {  
    657.                     Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");  
    658.                     return false;  
    659.                 }  
    660.   
    661.                 final float y = MotionEventCompat.getY(ev, pointerIndex);  
    662. //                final float yDiff = y - mInitialMotionY;  
    663.                 final float yDiff = y - mStartPoint;  
    664.   
    665.                 if((mLastDirection == Mode.PULL_FROM_START && yDiff < 0) ||  
    666.                         (mLastDirection == Mode.PULL_FROM_END && yDiff > 0))  
    667.                 {  
    668.                     return true;  
    669.                 }  
    670.                   
    671.                 if (!mIsBeingDragged && (yDiff > 0 && mLastDirection == Mode.PULL_FROM_START)  
    672.                         || (yDiff < 0 && mLastDirection == Mode.PULL_FROM_END)) {  
    673.                     mIsBeingDragged = true;  
    674.                 }  
    675.   
    676.                 if (mIsBeingDragged) {  
    677.                     // User velocity passed min velocity; trigger a refresh  
    678.                     if (yDiff > mDistanceToTriggerSync) {  
    679.                         // User movement passed distance; trigger a refresh  
    680.                         if(mLastDirection == Mode.PULL_FROM_END)  
    681.                         {  
    682.                             return true;  
    683.                               
    684.                         }  
    685.                         if ((mMode == Mode.PULL_FROM_START) || (mMode == Mode.BOTH))  
    686.                         {  
    687.                             mLastDirection = Mode.PULL_FROM_START;  
    688.                             startRefresh();  
    689.                         }  
    690.                     }  
    691.                     else if (-yDiff > mDistanceToTriggerSync) {  
    692.                         if((!up && !down && !loadNoFull)  || mLastDirection == Mode.PULL_FROM_START)  
    693.                         {  
    694.                             return true;  
    695.                         }  
    696.                         if ((mMode == Mode.PULL_FROM_END) || (mMode == Mode.BOTH))  
    697.                         {  
    698.                             mLastDirection = Mode.PULL_FROM_END;  
    699.                             startLoad();  
    700.                         }  
    701.                     }else {  
    702.                         if (!up && !down && yDiff < 0 && !loadNoFull)  
    703.                         {  
    704.                             return true;  
    705.                         }  
    706.                         // Just track the user's movement  
    707.                         //根据手指移动距离设置进度条显示的百分比  
    708.                         setTriggerPercentage(  
    709.                                 mAccelerateInterpolator.getInterpolation(  
    710.                                         Math.abs(yDiff) / mDistanceToTriggerSync));  
    711.                         updateContentOffsetTop((int)yDiff);  
    712.                         if (mTarget.getTop() == getPaddingTop()) {  
    713.                             // If the user puts the view back at the top, we  
    714.                             // don't need to. This shouldn't be considered  
    715.                             // cancelling the gesture as the user can restart from the top.  
    716.                             removeCallbacks(mCancel);  
    717.                             mLastDirection = Mode.DISABLED;  
    718.                         } else {  
    719.                             mDirection = (yDiff > 0 ? 1 : -1);  
    720.                             updatePositionTimeout();  
    721.                         }  
    722.                     }  
    723.                     mLastMotionY = y;  
    724.                 }  
    725.                 break;  
    726.   
    727.             case MotionEventCompat.ACTION_POINTER_DOWN: {  
    728.                 final int index = MotionEventCompat.getActionIndex(ev);  
    729.                 mLastMotionY = MotionEventCompat.getY(ev, index);  
    730.                 mActivePointerId = MotionEventCompat.getPointerId(ev, index);  
    731.                 break;  
    732.             }  
    733.   
    734.             case MotionEventCompat.ACTION_POINTER_UP:  
    735.                 onSecondaryPointerUp(ev);  
    736.                 break;  
    737.   
    738.             case MotionEvent.ACTION_UP:  
    739.             case MotionEvent.ACTION_CANCEL:  
    740.                 mIsBeingDragged = false;  
    741.                 mCurrPercentage = 0;  
    742.                 mActivePointerId = INVALID_POINTER;  
    743.                 mLastDirection = Mode.DISABLED;  
    744.                 return false;  
    745.         }  
    746.   
    747.         return true;  
    748.     }  
    749.   
    750.     private void startRefresh() {  
    751.         if (!mLoading && !mRefreshing)  
    752.         {  
    753.             removeCallbacks(mCancel);  
    754.             mReturnToStartPosition.run();  
    755.             setRefreshing(true);  
    756.             mRefreshListener.onRefresh();             
    757.         }  
    758.     }  
    759.   
    760.     private void startLoad() {  
    761.         if (!mLoading && !mRefreshing)  
    762.         {  
    763.             removeCallbacks(mCancel);  
    764.             mReturnToStartPosition.run();  
    765.             setLoading(true);  
    766.             mLoadListener.onLoad();   
    767.         }  
    768.     }  
    769.   
    770.     //手指移动时更新子控件的位置  
    771.     private void updateContentOffsetTop(int targetTop) {  
    772.         final int currentTop = mTarget.getTop();  
    773.         if (targetTop > mDistanceToTriggerSync) {  
    774.             targetTop = (int) mDistanceToTriggerSync;  
    775.         }  
    776.         //注释掉,否则上拉的时候子控件会向下移动  
    777. //        else if (targetTop < 0) {  
    778. //            targetTop = 0;  
    779. //        }  
    780.         setTargetOffsetTopAndBottom(targetTop - currentTop);  
    781.     }  
    782.   
    783.     //根据偏移量对子控件进行移动  
    784.     private void setTargetOffsetTopAndBottom(int offset) {  
    785.         mTarget.offsetTopAndBottom(offset);  
    786.         mCurrentTargetOffsetTop = mTarget.getTop();  
    787.     }  
    788.   
    789.     private void updatePositionTimeout() {  
    790.         removeCallbacks(mCancel);  
    791.         postDelayed(mCancel, RETURN_TO_ORIGINAL_POSITION_TIMEOUT);  
    792.     }  
    793.   
    794.     private void onSecondaryPointerUp(MotionEvent ev) {  
    795.         final int pointerIndex = MotionEventCompat.getActionIndex(ev);  
    796.         final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);  
    797.         if (pointerId == mActivePointerId) {  
    798.             // This was our active pointer going up. Choose a new  
    799.             // active pointer and adjust accordingly.  
    800.             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;  
    801.             mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);  
    802.             mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);  
    803.         }  
    804.     }  
    805.   
    806.     /** 
    807.      * Classes that wish to be notified when the swipe gesture correctly 
    808.      * triggers a refresh should implement this interface. 
    809.      */  
    810.     public interface OnRefreshListener {  
    811.         public void onRefresh();  
    812.     }  
    813.   
    814.     public interface OnLoadListener {  
    815.         public void onLoad();  
    816.     }  
    817.       
    818.     public void setMode(Mode mode)  
    819.     {  
    820.         this.mMode = mode;  
    821.     }  
    822.       
    823.     public void setLoadNoFull(boolean load)  
    824.     {  
    825.         this.loadNoFull = load;  
    826.     }  
    827.       
    828.     public static enum Mode {  
    829.         /** 
    830.          * Disable all Pull-to-Refresh gesture and Refreshing handling 
    831.          */  
    832.         DISABLED(0x0),  
    833.   
    834.         /** 
    835.          * Only allow the user to Pull from the start of the Refreshable View to 
    836.          * refresh. The start is either the Top or Left, depending on the 
    837.          * scrolling direction. 
    838.          */  
    839.         PULL_FROM_START(0x1),  
    840.   
    841.         /** 
    842.          * Only allow the user to Pull from the end of the Refreshable View to 
    843.          * refresh. The start is either the Bottom or Right, depending on the 
    844.          * scrolling direction. 
    845.          */  
    846.         PULL_FROM_END(0x2),  
    847.   
    848.         /** 
    849.          * Allow the user to both Pull from the start, from the end to refresh. 
    850.          */  
    851.         BOTH(0x3);  
    852.   
    853.         static Mode getDefault() {  
    854.             return BOTH;  
    855.         }  
    856.   
    857.         boolean permitsPullToRefresh() {  
    858.             return !(this == DISABLED);  
    859.         }  
    860.         boolean permitsPullFromStart() {  
    861.             return (this == Mode.BOTH || this == Mode.PULL_FROM_START);  
    862.         }  
    863.         boolean permitsPullFromEnd() {  
    864.             return (this == Mode.BOTH || this == Mode.PULL_FROM_END);  
    865.         }  
    866.   
    867.         private int mIntValue;  
    868.   
    869.         // The modeInt values need to match those from attrs.xml  
    870.         Mode(int modeInt) {  
    871.             mIntValue = modeInt;  
    872.         }  
    873.   
    874.         int getIntValue() {  
    875.             return mIntValue;  
    876.         }  
    877.   
    878.     }  
    879.       
    880.     /** 
    881.      * Simple AnimationListener to avoid having to implement unneeded methods in 
    882.      * AnimationListeners. 
    883.      */  
    884.     private class BaseAnimationListener implements AnimationListener {  
    885.         @Override  
    886.         public void onAnimationStart(Animation animation) {  
    887.         }  
    888.   
    889.         @Override  
    890.         public void onAnimationEnd(Animation animation) {  
    891.         }  
    892.   
    893.         @Override  
    894.         public void onAnimationRepeat(Animation animation) {  
    895.         }  
    896.     }  
    897. }  
     
  • 相关阅读:
    Docker学习笔记之常见 Dockerfile 使用技巧
    Docker学习笔记之通过 Dockerfile 创建镜像
    Docker学习笔记之保存和共享镜像
    Linux学习笔记之Linux环境变量总结
    Docker学习笔记之Docker的数据管理和存储
    Docker学习笔记之为容器配置网络
    Prometheus监控学习笔记之360基于Prometheus的在线服务监控实践
    Java学习笔记之Linux下的Java安装和配置
    Prometheus监控学习笔记之教程推荐
    ROS学习笔记(一) # ROS参数服务器
  • 原文地址:https://www.cnblogs.com/earl-yongchang/p/4936302.html
Copyright © 2020-2023  润新知