Android drop-down refresh control swiperefreshlayout source Analysis

Source: Internet
Author: User

Swiperefreshlayout is the official Android drop-down refresh control, the use of simple, beautiful interface, unfamiliar friends can casually search to understand, here is no nonsense, directly into the topic.

The principle of this pull-down refresh control is not difficult, basically is to listen to the movement of the finger, get the coordinates of the finger, through the calculation to determine what kind of operation, and then callback the corresponding interface. Swiperefreshlayout is inherited from the viewgroup, according to the Android event distribution mechanism, the touch event should be passed to ViewGroup first, according to the return value of Onintercepttouchevent to decide whether to intercept the event, Then Onintercepttouchevent departure:

@Override public boolean onintercepttouchevent (motionevent ev) {ensuretarget ();        Final int action = motioneventcompat.getactionmasked (EV);        if (mreturningtostart && action = = motionevent.action_down) {Mreturningtostart = false; } if (!isenabled () | | mreturningtostart | | canchildscrollup () | | mrefreshing | | mnestedscrollinprogr        ESS) {//Fail fast if we ' re not in a state where a swipe is possible return false; } switch (action) {Case MotionEvent.ACTION_DOWN:setTargetOffsetTopAndBottom (Moriginalof                Fsettop-mcircleview.gettop (), true);                Mactivepointerid = Motioneventcompat.getpointerid (EV, 0);                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 has an active pointer ID.");                return false;                Final float y = getmotioneventy (ev, Mactivepointerid);                if (y = =-1) {return false;                } final Float Ydiff = y-minitialdowny;                    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; }

There are many kinds of interception, if one of the five conditions to return false, when the use of touch events conflict can be analyzed from here, and here is not specifically expanded. A simple look at the Action_down in the finger coordinates, action_move calculate the distance to move, and to determine whether the threshold is greater than the value, yes, the misbeingdragged flag bit is set to True,action_ The misbeingdragged is set to false in the up. The final return is misbeingdragged.

Swiperefreshlayout is generally a nested scrollable view used, normal scrolling will meet the previous conditions, then do not intercept, only when scrolling to the top of the decision to enter the action. Misbeingdragged is true during finger press and lift, that is, interception, then how to handle it, and see ontouchevent:

@Override public boolean ontouchevent (motionevent ev) {.... switch (action) {case Mo                TionEvent.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 has an invalid ACTI                    ve 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 has an active pointer ID.");                return false;                Final float y = motioneventcompat.gety (ev, POINTERINDEX);                Final float overscrolltop = (y-minitialmotiony) * drag_rate;                Misbeingdragged = false;                Finishspinner (Overscrolltop);                Mactivepointerid = Invalid_pointer;            return false;        } case MotionEvent.ACTION_CANCEL:return false;    } return true; }

Some of the code is omitted here, and a few lines ahead are similar to the above, and are returned directly when one of the conditions is satisfied, and a few lines in the switch handle multi-finger touch, which are skipped. Look at the Action_move in the calculation of the distance of the finger movement, then the misbeingdragged normally should be true, when the distance greater than 0 will be executed Movespinner. The Finishspinner is executed in action_up, and it is possible to guess that the logic to perform the refresh is mainly in both methods.

Before looking at these two methods, you should know two important member variables: One is Mcircleview, is an instance of Circleimageview, inherits ImageView, the background of the main drawing progress Circle, the other is mprogress, is an instance of Materialprogressdrawable, inherits from Drawable and implements Animatable interface, mainly draws the progress circle, Swiperefreshlayout is to draw the animation by calling its method. Now let's look at Movespinner:

<span style= "FONT-SIZE:18PX;" >
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 */); }</span>
Showarrow is the display arrow, the middle of the lump is also some math and set the style of the progress Circle, the second-to-last line of the execution of the setprogressrotation, passed through a pile of computed rotation, the heap calculation is mainly the optimization effect, For example, in the beginning of the move when the growth is relatively fast, more than the refresh of the distance after the growth is relatively slow. When the method is passed in, Mprogress draws the progress circle according to it, so the main animation should be within this method. The last line executes Settargetoffsettopandbottom, let's take a look:
<span style= "FONT-SIZE:18PX;" >private void Settargetoffsettopandbottom (int offset, Boolean requiresupdate) {        mcircleview.bringtofront ();        Mcircleview.offsettopandbottom (offset);        Mcurrenttargetoffsettop = Mcircleview.gettop ();        if (requiresupdate && Android.os.Build.VERSION.SDK_INT < one) {            invalidate ();        }    } </span>

It's easier to adjust the position of the progress circle and record it. Finally, take a look at Finishspinner:
<span style= "FONT-SIZE:18PX;" >private void Finishspinner (float overscrolltop) {if (Overscrolltop > Mtotaldragdistance) {setre        Freshing (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) {start                        Scaledownanimation (NULL); }} @Override public void Onanimationrepeat (Animation Animation)            {                    }                }; } AnimateoffsettostarTposition (mcurrenttargetoffsettop, listener);        Mprogress.showarrow (FALSE); }}</span>

logic is also very simple, when the distance moved beyond the set value of the execution of Setrefreshing (True,true), Updating the values of some member variables in this method executes the animateoffsettocorrectposition, which is known by the name as the execution of the animation to move the progress circle to the correct position (i.e., the head). If the distance moved does not exceed the set value, the animateoffsettostartposition is executed. Take a look at both the Animateoffsettocorrectposition and the animateoffsettostartposition methods:

<span style= "FONT-SIZE:18PX;"        >private void animateoffsettocorrectposition (int from, Animationlistener listener) {mfrom = from;        Manimatetocorrectposition.reset ();        Manimatetocorrectposition.setduration (animate_to_trigger_duration);        Manimatetocorrectposition.setinterpolator (Mdecelerateinterpolator);        if (listener! = null) {Mcircleview.setanimationlistener (listener);        } mcircleview.clearanimation ();    Mcircleview.startanimation (manimatetocorrectposition); } private void Animateoffsettostartposition (int from, Animationlistener listener) {if (Mscale) {//        Scale the item back to down Startscaledownreturntostartanimation (from, listener);            } else {mfrom = from;            Manimatetostartposition.reset ();            Manimatetostartposition.setduration (animate_to_start_duration);            Manimatetostartposition.setinterpolator (Mdecelerateinterpolator); if (listenEr! = null) {Mcircleview.setanimationlistener (listener);            } mcircleview.clearanimation ();        Mcircleview.startanimation (manimatetostartposition); }}</span>
The logic is basically the same, after some setup, the Mcircleview startanimation is executed, only the values passed in and the listener are different.

If the operation is to perform a refresh, the incoming value is the head height and the listener is:

<span Style= "FONT-SIZE:18PX;" >private Animation.animationlistener Mrefreshlistener = new Animation.animationlistener () {@Override Pub LIC 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 (); }}};</span> 
After the animation completes, that is, the progress circle moves to the head, will execute Mprogress.start (); Next notice that if Mlistener is not empty, the Onrefresh method is executed, and the Mlistener actually executes the listener set by Setonrefreshlistener, so the refresh is done here. If you are performing an operation back to the initial position, the incoming value is the initial height (that is, above the top) and the listener is
<span style= "FONT-SIZE:18PX;" >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) {    }};</span>
Moving to the initial position executes startscaledownanimation, which is the vanishing animation, and the entire refresh process ends here.
This is basically the process of swiperefreshlayout over, but to achieve such a control or there are a lot of small problems to consider, here is the main idea to clarify, know if there is a problem how to solve. In addition, from the source code can also be seen swiperefreshlayout customization is relatively poor, do not know Google is not intentionally so hope all in the future with this unified style of the drop-down refresh. Of course, there are some third-party pull-down refresh customization is still relatively good, it is not difficult to use. But some people (like me) tend to use the official controls rather than a third-party tool. Next time will write a discussion about using Swiperefreshlayout to implement custom style articles ~

Android drop-down refresh control swiperefreshlayout source Analysis

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.