Android Drop-down Refresh Control Swiperefreshlayout Source Resolution _android

Source: Internet
Author: User

Swiperefreshlayout is the official Android Drop-down Refresh control, the use of simple, beautiful interface, unfamiliar friends can be random search to understand, here is no nonsense, directly into the business.

First of all, give a flowchart, marked the role of several main methods, can be combined to see ha.

This Drop-down refresh control principle is not difficult, the basic is to monitor the movement of the fingers, get the coordinates of the finger, through the calculation of what kind of operation, and then callback the corresponding interface. Swiperefreshlayout is inherited from ViewGroup, according to the Android event distribution mechanism, the touch event should be first passed to the ViewGroup, according to the Onintercepttouchevent return value to determine whether to intercept the event, Then onintercepttouchevent to go:

@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 | | mnestedscrollinprogress) {//Fa
  Il fast if we ' re not in a state where a swipe be possible return false; Switch (action) {Case MotionEvent.ACTION_DOWN:setTargetOffsetTopAndBottom (moriginaloffsettop-mcircleview.g
    Ettop (), 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 bu T 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;
     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, here if one of the five conditions to return directly false, when the use of touch events conflict can be analyzed from here, this is not specifically launched. A simple look, in the Action_down to record the coordinates of the finger, action_move in the calculation of the moving distance, and to determine whether greater than the threshold, yes, the MISBEINGDRAGGED flag will be set to True,action_ Up, the misbeingdragged is set to false. The final return is misbeingdragged.

Swiperefreshlayout is generally a nested, scrollable view used, the normal scrolling will meet the previous conditions, then do not intercept, only when scrolling to the top will enter the back action of judgment. Misbeingdragged is true when the fingers are pressed and raised, that is, to intercept, and then how to handle it, and see ontouchevent:

 @Override public boolean ontouchevent (motionevent ev) {.... 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 active invalid 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;
    Finishspinner (Overscrolltop);
    Mactivepointerid = Invalid_pointer;
   return false;
  Case MotionEvent.ACTION_CANCEL:return false;
 return true; 

 }

Some of the code is omitted, and a few lines ahead are similar to the above, and are returned directly when one of the conditions is met, and there are several lines in the switch that handle multiple-touch controls, which are skipped. Take a look at the distance in the Action_move that calculates the movement of the finger, the misbeingdragged normally should be true and the Movespinner will be executed when the distance is greater than 0. In Action_up, the Finishspinner is executed, where it is possible to guess that the logic for performing the refresh is primarily within these two methods.

Before looking at these two methods, you should know two important member variables: One is Mcircleview, the other is Circleimageview, inherits ImageView, mainly draws the background of the progress Circle, the other is mprogress, is a materialprogressdrawable instance, inherits from the Drawable and implements the Animatable interface, mainly draws the progress circle, swiperefreshlayout precisely by calls its method to draw the animation. Now let's take a 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.visi BLE);
  } 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)) {//Anima
   Te 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 one is mainly some math and set the style of the progress Loop, the penultimate line executes the setprogressrotation, passing through a heap of calculated rotation, this heap calculation is mainly the optimization effect, For example, at the beginning of the mobile growth is faster, more than the refresh distance after the growth is slower. When the method is passed in, Mprogress draws a progress loop based on it, so the main animation should be within this method. The last line executes Settargetoffsettopandbottom, let's take a look at:

 <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>

Relatively simple, is to adjust the position of the progress loop and record. Finally, take a look at Finishspinner:

 <span style= "FONT-SIZE:18PX;" >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 (Animat
       Ion animation) {} @Override public void Onanimationend (animation animation) {if (!mscale) {
      Startscaledownanimation (NULL);
   @Override public void Onanimationrepeat (Animation Animation) {}};
   } animateoffsettostartposition (mcurrenttargetoffsettop, listener);
  Mprogress.showarrow (FALSE); }}</span> 

  Logic is also simple to execute setrefreshing (true,true) when the distance moved exceeds the set value, and the value of some member variables is updated in the method to perform animateoffsettocorrectposition. By name you know it is the animation that moves the progress loop to the correct position (that is, the head). If the moved distance does not exceed the set value, the animateoffsettostartposition is executed. Let's take a look at Animateoffsettocorrectposition and Animateoffsettostartposition. Two 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 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, and after some setup, the Mcircleview startanimation will be executed, only the values passed in and the listener are different.

If you are performing a refresh operation, the value passed in is the head height, and the listener is:

 <span style= "FONT-SIZE:18PX;" >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 ();}}}
 ; </span>

After the animation is completed, that is, the progress loop is moved to the head, the Mprogress.start () is executed; Then notice that if the Mlistener is not empty, the Onrefresh method is executed, and this mlistener is actually the listener that is set up to perform Setonrefreshlistener, so the refresh is done here. If the action is performed back to the original 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 will perform startscaledownanimation, which is the vanishing animation, where the entire refresh process is over.

This basically put the swiperefreshlayout process over again, but to achieve such a control or have a lot of small problems to consider, here is the main idea to clarify, know if there are problems to solve. In addition from the source can also be seen swiperefreshlayout customization is relatively poor, also do not know whether Google is deliberately so hope that all later use this unified style Drop-down refresh. Of course, there are some third-party drop-down refresh customization is still relatively good, not difficult to use. But some people (like me) are more inclined to use the official controls than to use Third-party tools as a last resort. Next time I'll write an article about how to implement a custom style with Swiperefreshlayout ~

There is also a follow-up from the revision of Swiperefreshlayout source code from the definition of high imitation micro-letter friend Circle of the drop-pull refresh effect of the article, interested can look at Ha http://www.jb51.net/article/89311.htm

The above is the entire content of this article, I hope to help you learn, but also hope that we support the cloud habitat community.

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.