Android controls Refreshableview implementation drop-down refresh _android

Source: Internet
Author: User
Tags gety touch

Requirements: Customize a viewgroup, implement the ability to pull down the refresh. After a certain distance (drop down the interface can be customized to customize any complex interface) to release the finger can recall the refresh function, after the user processing the refreshed content, you can Invoke method Oncompleterefresh () notification refresh, and then return to normal state. The effect is as follows:

Source code: Refreshableview (Https://


Our aim is no matter what controls, as long as in the XML Chinese and foreign bread a layer of labels, that this label all the sub tags below the control is supported can be Drop-down refresh. So, we can use ViewGroup to achieve, here I choose to inherit LinearLayout, of course, the other (framelayout, etc.) are the same.

Because depending on your fingers, you need a refreshed view to be displayed, so here you need to add a child view (called Refreshheaderview) and place it at the top, and the benefits of using LinearLayout can be set to vertical, which can be directly "This.addview (Refreshheaderview, 0);" It's done. Then it is necessary to change the height of the refreshheaderview dynamically according to the distance of the finger sliding. Also detects whether the height that can be refreshed is reached, and if so, update the current refresh state. When the finger is released, perform a refresh or return to normal state according to the refresh state that was previously moved.

The Refreshableview code is as follows:

/** * Drop-down Refresh control * Author:wangjie * * DATE:12/13/14. * * public class Refreshableview extends LinearLayout {private static final String TAG = RefreshableView.class.getSimple

  Name ();
    Public Refreshableview {Super (context);
  Init (context);

    Public Refreshableview (context, AttributeSet attrs) {Super (context, attrs);
    TypedArray a = Context.obtainstyledattributes (Attrs, R.styleable.refreshableview);
      for (int i = 0, Len = a.length (); i < Len; i++) {int attrindex = A.getindex (i); Switch (attrindex) {Case r.styleable.refreshableview_interceptallmoveevents:interceptallmoveevents = a
          . Getboolean (I, false);

    } a.recycle ();
  Init (context);
    Public Refreshableview (context, AttributeSet attrs, int defstyle) {Super (context, attrs, Defstyle);
  Init (context); /** * Refresh status/public static final int State_Refresh_normal = 0x21;
  public static final int state_refresh_not_arrived = 0x22;
  public static final int state_refresh_arrived = 0x23;
  public static final int state_refreshing = 0x24;
  private int refreshstate;

  Refresh State Monitoring private refreshablehelper refreshablehelper;
  public void Setrefreshablehelper (Refreshablehelper refreshablehelper) {this.refreshablehelper = RefreshableHelper;
  Private context context;
  /** * Refreshing view/private view refreshheaderview;
  /** * Refreshing view of the true height * * private int originrefreshheight;
  /** * Effective down-pull refresh needs to reach the height * * private int refresharrivedstateheight;
  /** * The height of the display when refreshing * * private int refreshingheight;

  /** * Normal does not refresh the height * * private int refreshnormalheight;
  /** * The default does not allow blocking (that is, passing events to the child view), which is valid only when interceptallmoveevents is false/private Boolean disallowintercept = true;

  /** * XML can set its value to False, indicating that the moved event is not passed to the child control/private Boolean interceptallmoveevents; private void init (context context) {This.context = context;

This.setorientation (VERTICAL);
  LOG.D (TAG, "[init]originrefreshheight:" + refreshheaderview.getmeasuredheight ()); @Override protected void onsizechanged (int w, int h, int oldw, int oldh) {super.onsizechanged (W, H, OLDW, OLDH

    if (null!= refreshablehelper) {Refreshheaderview = Refreshablehelper.oninitrefreshheaderview ();
    }//Refreshheaderview = Layoutinflater.from (context). Inflate (r.layout.refresh_head, NULL);
      if (null = = Refreshheaderview) {log.e (TAG, "Refreshheaderview is null!");
    } this.removeview (Refreshheaderview);

    This.addview (Refreshheaderview, 0);
    Compute refreshheadview Dimensions int width = View.MeasureSpec.makeMeasureSpec (0, View.MeasureSpec.UNSPECIFIED);
    int expandspec = View.MeasureSpec.makeMeasureSpec (integer.max_value >> 2, View.MeasureSpec.AT_MOST);

    Refreshheaderview.measure (width, expandspec); LOG.D (TAG, "[Onsizechanged]w:" + W + ", H:"+ h);
    LOG.D (TAG, "[Onsizechanged]child counts:" + this.getchildcount ());

    Originrefreshheight = Refreshheaderview.getmeasuredheight ();
    Boolean isusedefault = true;
    if (null!= refreshablehelper) {isusedefault = Refreshablehelper.oninitrefreshheight (originrefreshheight);
      }//Initialize each height if (isusedefault) {refresharrivedstateheight = Originrefreshheight;
      Refreshingheight = Originrefreshheight;
    refreshnormalheight = 0;
    LOG.D (TAG, "[Onsizechanged]refreshheaderview Origin Height:" + originrefreshheight);

    Changeviewheight (Refreshheaderview, refreshnormalheight);

  Initialized to normal state setrefreshstate (state_refresh_normal); @Override public boolean dispatchtouchevent (motionevent ev) {LOG.D (TAG, "[Dispatchtouchevent]ev Action:" + EV
    . Getaction ());
  return super.dispatchtouchevent (EV); @Override public boolean onintercepttouchevent (MotiOnEvent ev) {LOG.D (TAG, "[Onintercepttouchevent]ev Action:" + ev.getaction ());
    if (!interceptallmoveevents) {return!disallowintercept; //If set to block all move events, that is, interceptallmoveevents is true if (Motionevent.action_move = = Ev.getaction ()) {return TR
  return false; @Override public void Requestdisallowintercepttouchevent (Boolean disallowintercept) {if this.disallowintercep
    t = = disallowintercept) {return;
    } this.disallowintercept = disallowintercept;
  Super.requestdisallowintercepttouchevent (disallowintercept);

  private float DownY = Float.max_value;
    @Override public boolean ontouchevent (Motionevent event) {//Super.ontouchevent (event);
    LOG.D (TAG, "[Ontouchevent]ev Action:" + event.getaction ());
        Switch (event.getaction ()) {Case MotionEvent.ACTION_DOWN:downY = event.gety ();
        LOG.D (TAG, "Down--> DownY:" + DownY); Requestdisallowintercepttouchevent (TRUE); // Ensure that events can be passed down the break;
        Case MotionEvent.ACTION_MOVE:float Cury = Event.gety ();
        float DeltaY = cury-downy;
        Whether it is a valid drag down event (you need to display the loaded header) Boolean isdropdownvalidate = Float.max_value!= DownY; /** * Modify Interception settings * If this is a valid down-drag event, the event needs to be handled in this viewgroup, so it needs to be blocked from being passed to the child control, that is, not allowed to intercept set to False * If the event is effectively dragged down, the event is passed to the

        child controls, so there is no need to intercept and pass to the child control, that is, blocking is not allowed to true/requestdisallowintercepttouchevent (!isdropdownvalidate);
        DownY = Cury;
        LOG.D (TAG, "Move--> DeltaY (cury-downy):" + DeltaY);
        int curheight = Refreshheaderview.getmeasuredheight ();

        int exceptheight = curheight + (int) (DELTAY/2); If you are not currently in a refreshing state, update the Refresh status if (state_refreshing!= refreshstate) {if (Curheight >= refresharrivedstat
          Eheight) {//To achieve a refresh state setrefreshstate (state_refresh_arrived);
          else {//failed to reach the refresh State setrefreshstate (state_refresh_not_arrived);
     }   } if (isdropdownvalidate) {changeviewheight (Refreshheaderview, Math.max (Refreshnormalheight, ExceptH
        eight)); else {//prevent downy caused by the modification of the child control from the Float.max_value problem Changeviewheight (Refreshheaderview, Math.max (Curheight, EXCEP
      } break;
        Case MotionEvent.ACTION_UP:downY = Float.max_value;
        LOG.D (TAG, "Up--> DownY:" + DownY); Requestdisallowintercepttouchevent (TRUE);
          Ensure that events can be passed down//If the refresh state is reached, set the height of the refresh state if (state_refresh_arrived = = refreshstate) {//reached the state of the refresh
          Startheightanimation (Refreshheaderview, Refreshheaderview.getmeasuredheight (), refreshingheight);
        Setrefreshstate (state_refreshing); Or else if (state_refreshing = = refreshstate) {//state being refreshed startheightanimation (Refreshheaderview, Refreshheaderv
        Iew.getmeasuredheight (), refreshingheight); else {///Perform animation back to normal state startheightanimation (Refreshheaderview, RefreShheaderview.getmeasuredheight (), Refreshnormalheight, Normalanimatorlistener);
      } break;
  return true; /** * This method is called after the refresh is complete * * public void Oncompleterefresh () {if (state_refreshing = = refreshstate) {setre
      Freshstate (State_refresh_normal);
    Startheightanimation (Refreshheaderview, Refreshheaderview.getmeasuredheight (), refreshnormalheight); }/** * Modifies the current refresh state * * @param expectrefreshstate/private void setrefreshstate (int expectrefreshstat
      e) {if (expectrefreshstate!= refreshstate) {refreshstate = expectrefreshstate;
      if (null!= refreshablehelper) {refreshablehelper.onrefreshstatechanged (Refreshheaderview, refreshState); /** * Change the height of a control * @param view * @param height */private void changeviewheight (view view , int height) {log.d (TAG, "[Changeviewheight]change Height: "+ height";
    Viewgroup.layoutparams LP = View.getlayoutparams ();
    Lp.height = height;
  VIEW.SETLAYOUTPARAMS (LP); /** * Change the height of a control animation * * @param view * @param fromheight * @param toheight/private void Startheigh
  Tanimation (final view view, int fromheight, int toheight) {startheightanimation (View, fromheight, toheight, NULL); } private void Startheightanimation (final view view, int fromheight, int toheight, Animator.animatorlistener animatorl
    Istener) {if (Toheight = = View.getmeasuredheight ()) {return;
    } valueanimator heightanimator = Valueanimator.ofint (Fromheight, toheight); Heightanimator.addupdatelistener (New Valueanimator.animatorupdatelistener () {@Override public void Onanimatio
        Nupdate (Valueanimator valueanimator) {integer value = (integer) valueanimator.getanimatedvalue ();
        if (null = = value) return;
      Changeviewheight (view, value);
    }); if (null!= animaTorlistener) {Heightanimator.addlistener (Animatorlistener);
    } heightanimator.setinterpolator (New Linearinterpolator ());
    Heightanimator.setduration (300/*ms*/);
  Heightanimator.start (); } animatorlisteneradapter Normalanimatorlistener = new Animatorlisteneradapter () {@Override public void Onanim
      Ationend (animator animation) {Super.onanimationend (animation); Setrefreshstate (State_refresh_normal);

  Return to normal Status}}; public void setrefresharrivedstateheight (int refresharrivedstateheight) {this.refresharrivedstateheight = RefreshArr
  The public void setrefreshingheight (int refreshingheight) {this.refreshingheight = Refreshingheight;
  The public void setrefreshnormalheight (int refreshnormalheight) {this.refreshnormalheight = Refreshnormalheight;
  public int getoriginrefreshheight () {return originrefreshheight;



Originrefreshheight, which represents the actual height of the head refresh view.

Refresharrivedstateheight, indicating how far down the pull can perform the refresh

Refreshingheight, indicating how much of the height is displayed when refreshing

Refreshnormalheight, indicating how high the Refreshheaderview display is in normal condition.

The main core code should be in the Ontouchevent method, the first simple analysis of the main code: in Action_down, record the current finger whereabouts of the y-coordinate, and then action_move, to calculate the sliding distance, And to judge if the distance of the slide is greater than refresharrivedstateheight, the update is in a state that can be refreshed, whereas the update is not in a state that can be refreshed. Then in Action_up, if you have reached a state that can be refreshed, update the method that the current state is refreshing and the callback state changes.

What should we do with the events inside if there are scrollview and other controls that can be scrolled?

Take Https:// as an example

The XML layout is as follows:

<com.wangjie.refreshableview.refreshableview xmlns:rv= " Com.wangjie.refreshableview "android:id=" @+id/main_refresh_view "android:layout_width=" Match_parent "and roid:layout_height= "Match_parent" rv:interceptallmoveevents= "false" > &LT;COM.WANGJIE.REFRESHABLEVIEW.N Estscrollview android:layout_width= "match_parent" android:layout_height= "Wrap_content" true
          "> <textview android:id=" @+id/main_tv "android:layout_width=" Fill_parent " android:layout_height= "wrap_content" android:gravity= "center" android:padding= "20DP" and Roid:textsize= "18SP" android:text= "Drop down for Refresh\n\n\n\n\n\n\n\n\n\n\ndrop down for Refresh\ndrop down F or Refresh\n\n\n\n\n\n\n\n\n\n\ndrop down for refresh\ndrop down for refresh\n\n\n\n\n\n\n\n\n\n\ndrop down for Refresh\ Ndrop Down for Refresh\n\n\n\n\n\n\n\n\n\n\ndropDown for Refresh "/> </com.wangjie.refreshableview.NestScrollView> </com.wangjie.refreshablev Iew.


As above, the outermost is a refreshableview, and then inside is a nestscrollview,nestscrollview inside is TextView, which textview because of the text more, So use Nestscrollview to implement scrolling (Nestscrollview extended from ScrollView, as described below). The logic at this point is that when the Nestscrollview is at the top, the finger slides down, and the touch event should be handed over to Refreshableview; when the finger slides up, that is, ScrollView scrolls down, The touch event needs to be addressed to Refreshableview.

The following analysis of the code inside:

Refreshableview's interceptallmoveevents Indicates whether Refreshableview is required to block all move events (that is, all move events are handled by Refreshableview itself). This can be set to true if there is no scrollview in the control, such as controls that need to handle move events, and if there are controls such as ScrollView, you need to set to false so that Refreshableview passes the move event to the subclass. To be processed by subclasses. Obviously, the case in the example now is the need to set interceptallmoveevents to false. The method you set can look at the XML file above and use the rv:interceptallmoveevents= "false" attribute.

Onintercepttouchevent () method, we return the disallowintercept, this disallowintercept is based on requestdisallowintercepttouchevent () method to dynamically change, so that you can switch the processing object of the touch event.

When the finger falls, first call the Requestdisallowintercepttouchevent () method to ensure that the current event can be passed to the child control normally, that is, the present ScrollView. Then the finger will start to move, in the Action_move, first calculate the current sliding distance.

If the event is effectively dragged down, the event needs to be handled in Refreshableview, so it needs to be blocked from being passed to the child control, that is, not allowed to be set to false, or if the event is not effectively dragged down, the event is passed to the child control, so there is no need to intercept and pass That is, blocking is not allowed to be set to true.

How to judge if it works? According to Downy, if the downy is the original initial value float.max_value, it means that the move event started down as a quilt control rather than refreshableview processing. This is an invalid drag-and-drop event for Refreshableview, and if Downy is not the original initial value Float.max_value, the Move event is already Refreshableview handled at down time. So it's effective.

Then, the height of the Refreshheaderview is computed and the height of the Refreshheaderview is transformed according to the sliding difference.

If the current state is refreshing, the move event is directly invalid.

Otherwise, determine whether the current height is up to a refreshed state or not, and update the status value without the refresh status.

In the up, or to ensure that the event passed down first. and reset the downy. Then, depending on the current state, if the state of the refresh is reached, the refresh is started and the current amount state is refreshed, and if the refresh state is not reached, the animation returns to its normal state, and if the state is refreshing, the animation reverts to the height of the refresh.

Then analyze the next Nestscrollview:

/** * Author:wangjie * * DATE:12/13/14. * * public class Nestscrollview extends ScrollView {private static final String TAG = NestScrollView.class.getSimpleName

  Public Nestscrollview {Super (context);
  Public Nestscrollview (context, AttributeSet attrs) {Super (context, attrs);
  Public Nestscrollview (context, AttributeSet attrs, int defstyle) {Super (context, attrs, Defstyle);  @Override public boolean dispatchtouchevent (motionevent ev) {LOG.D (TAG, "___[dispatchtouchevent]ev action:" +
    Ev.getaction ());
  return super.dispatchtouchevent (EV);
    @Override public boolean onintercepttouchevent (motionevent ev) {super.onintercepttouchevent (EV);
    LOG.D (TAG, "___[onintercepttouchevent]ev action:" + ev.getaction ());
    if (Motionevent.action_move = = Ev.getaction ()) {return true;
  return false;

  float Lastdowny; @Override Public BooleAn ontouchevent (Motionevent event) {super.ontouchevent (event);
        Switch (event.getaction ()) {Case MotionEvent.ACTION_DOWN:lastDownY = event.gety (); Parentrequestdisallowintercepttouchevent (TRUE);
        Ensure that events can be passed down log.d (TAG, "___down");
return true;
        Case MOTIONEVENT.ACTION_MOVE:LOG.D (TAG, "___move, this.getscrolly ():" + this.getscrolly ());
        Boolean istop = Event.gety ()-lastdowny > 0 && this.getscrolly () = = 0;
          if (istop) {//allows the parent control to intercept, that is, the parent control is not allowed to intercept set to False parentrequestdisallowintercepttouchevent (false);
        return false;
          else {//do not allow the parent control to intercept, that is, the parent control is not allowed to intercept the set to True Parentrequestdisallowintercepttouchevent (true);
        return true;
        Case MOTIONEVENT.ACTION_UP:LOG.D (TAG, "___up, this.getscrolly ():" + this.getscrolly ()); Parentrequestdisallowintercepttouchevent (TRUE);
      Ensure that events can be passed down the break; Case MotionevEnt.
        ACTION_CANCEL:LOG.D (TAG, "___cancel");
  return false; /** * Allow parent control to intercept event * @param disallowintercept/private void parentrequestdisallowintercepttouchevent (bool
    Ean disallowintercept) {viewparent VP = GetParent ();
    if (NULL = VP) {return;
  } vp.requestdisallowintercepttouchevent (disallowintercept);



As shown above, you also need to rewrite the Onintercepttouchevent () method, which needs to pass all events except the move event, so that the innermost textview have onclick events.

In the Ontouchevent method, the y-coordinate of the down is recorded in the Action_down, then the event of the parent (i.e., Refreshableview) can be passed over, so the call to GetParent () is required. Requestdisallowintercepttouchevent () method. Because a drop-down refresh can only occur at the top of the ScrollView scroll bar, in move, if the current state is at the top, you need to have the parent control (Refreshableview) intercept and then return directly to false. Let the current event pass to the Ontouchevent method in Refreshableview. If not at top, then mask the processing of the calling parent control (Refreshableview) and handle it directly. Finally, make sure that the event can be passed to scrollview here when up.

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.

Related Article

E-Commerce Solutions

Leverage the same tools powering the Alibaba Ecosystem

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

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: and provide relevant evidence. A staff member will contact you within 5 working days.