Pullscrollview Source Code Analysis

Source: Internet
Author: User
Tags gety

Pullscrollview is an open source project above GitHub, which is mainly used to achieve the effect of pull-down head stretching, project address see Https://github.com/MarkMjw/PullScrollView

Let's take a look at the implementation (Image invasion and deletion)


But because the code of this project is more complex, and actually do not do this also can achieve effect, I found another article on the internet Introduction Pullscrollview article, write very good, this article is only based on the article http://blog.csdn.net/harvic880925/ Article/details/46728247 talk about their own understanding and harvest, if you want to know more detailed, you can visit this article.


Let me first explain the implementation of the idea, the overall layout is a scrollview, meaning that we have to inherit scrollview to implement their own control Pullscrollview, for the pull-down will have the effect of the picture, in fact, is a layout outside the ScrollView , Pullscrollview provides a SetHeader () method to set the drop-down head for itself.

We can set any of the view to Pullscrollview's head, as long as the Pullscrollview is dragged, change the layout of the view can be, we call this view as Headerview

So how exactly did that change? Through the layout () method to change the position of the Headerview, drag, constantly modify the Headerview position so that the move down, but this is only to achieve the effect of drag, to have a similar effect of stretching, We need to change the Headerview margintop to negative, so that when the position moves, it feels like Headerview will be stretched.

The better effect is that the bottom is elongated too, so it looks like it was pulled together on both sides. In the picture we see Headerview below the main layout, which we call Contentview, we let Contentview cover part of the Headerview, and then let Contentview and headerview the drop rate is not the same, This can cause the bottom to also lengthen the effect.


Now think about how to use layout ()? Obviously we have to listen to the down and move events of the fingers, down when the position is recorded, the move when the current position minus down position, this difference is the headerview position of the growth distance, As for Contentview we can multiply it by a decimal number to make it smaller.

Theoretically we achieve the effect of pull down, but there are a lot of flaws, now we look at the source code first.

The first is the properties and construction methods

public class Pullscrollview extends ScrollView {//Bottom picture view private View Mheaderview;    The initial position of the head picture is private rect mheadinitrect = new rect ();    Bottom view private view mcontentview;    The initialization position of the contentview of the ScrollView private Rect mcontentinitrect = new rect ();    Initial click location private point mtouchpoint = new points ();    Identifies whether the current view moves Boolean mismoving = false;    Whether to suppress the movement of the control itself Boolean menablemoving = false;    Whether to use the layout function to move the layouts Boolean mislayout = false;     /** * Damping coefficient, the smaller the resistance is greater.    */private static final float scroll_ratio = 0.5f;    private int mcontenttop, Mcontentbottom;    private int mheadercurtop, Mheadercurbottom;    User-defined Headview height private int mheaderheight = 0;        Public Pullscrollview (context context, AttributeSet Attrs) {Super (context, attrs);    Init (context, attrs); } public Pullscrollview (context context, AttributeSet attrs, int defstyleattr) {Super (context, Attrs, Defstylea        TTR);   Init (context, attrs); } private void init (context context, AttributeSet attrs) {//Set scroll mode Setoverscrollmode (OVER_SCR        Oll_never);            if (null! = attrs) {TypedArray ta = context.obtainstyledattributes (attrs, R.styleable.pullscrollview);                if (ta! = null) {mheaderheight = (int) ta.getdimension (r.styleable.pullscrollview_headerheight,-1);            Ta.recycle (); }        }    }
For member properties you can see the comments, we initialized two rect, and a point. For the Init () method, it is actually getting two of our custom properties.

Note that the position of the scrollview here is so unchanged that it changes the contentview inside the ScrollView, so we use Setoverscrollmode (over_scroll_never) to limit its movement.

And then, according to the above, we have a SetHeader () method.

public void Setmheaderview (view view) {        Mheaderview = view;    }

In addition, we have access to the contentview to make it easier to modify its location, and this fetch must be done after the layout has finished rendering

@Override    protected void Onfinishinflate () {        if (Getchildcount () > 0) {            Mcontentview = getchildat (0);        }        super.onfinishinflate ();    }


The next step is to listen to events.

@Override public boolean onintercepttouchevent (Motionevent event) {if (event.getaction () = = Motionevent.action_            Down) {//Save original position mtouchpoint.set ((int) event.getx (), (int) event.gety ());            Mheadinitrect.set (Mheaderview.getleft (), Mheaderview.gettop (), Mheaderview.getright (), Mheaderview.getbottom ()); Mcontentinitrect.set (Mcontentview.getleft (), Mcontentview.gettop (), Mcontentview.getright (),            Mcontentview.getbottom ());            Mismoving = false;            If you are not currently scrolling from the initialization position, do not let the user drag if (getscrolly () = = 0) {mislayout = true; }} else if (event.getaction () = = Motionevent.action_move) {//If the current event is an event we want to handle, such as the drop-down now, we cannot let the child control Rationale for this event//you need to intercept it here, not to the child control, not to let the child control consume this event//otherwise the behavior of the child control may conflict with ours int deltay = (int) event.gety            ()-Mtouchpoint.y; DeltaY = DeltaY < 0?            0: (DeltaY > Mheaderheight mheaderheight:deltay); if (deltaY > 0 && deltay >= getscrolly () && getscrolly () = = 0) {ontouchevent (event);            return true;    }} return Super.onintercepttouchevent (event); }
Some wonder, why is it in the Onintercepttouchevent (Motionevent event) method, rather than ontouchevent (Motionevent event)?

This actually involves an event interception and delivery mechanism, first we have to understand that for ScrollView this groupview, when we press the finger, the first call is the Dispatchtouchevent () method, this method is used to distribute the touch event, Next, Onintercepttouchevent (Motionevent event) is called by default. This method is used for event interception, and if true, the event is no longer passed down, meaning it is no longer distributed to child controls.

If False is returned, it is distributed to child controls, which are processed by the Ontouchevent () method of the child control, and if the Ontouchevent () method of the sub-control returns false, the event is then distributed to the ScrollView ontouchevent () The method is processed and returns true without distributing the ontouchevent () method to ScrollView.

It says the touch event distribution sequence is as follows:

Parent control's Onintercepttouchevent (Motionevent event)---child control ontouchevent ()--ntouchevent () of the parent control

In the above pass, any one returns true, and the distribution is not continued downward.


OK, now let's understand the above notation, we get down in the Onintercepttouchevent (Motionevent event) method to avoid the child controls consuming events (as the author of Pullscrollview, We cannot guarantee that the user's child controls will not be consumed.


Also, when down, we recorded the position of the finger, which was recorded using Mtouchpoint.

The initial position of the Headerview is recorded, which is recorded using Mheadinitrect.

The initial position of the Contentview is recorded, which is recorded using Mcontentinitrect.

Other settings We don't care about for the time being.


And then when we look at the move,

int deltay = (int) event.gety ()-Mtouchpoint.y;deltay = DeltaY < 0? 0: (DeltaY > Mheaderheight mheaderheight:deltay);
Note here that we deal with the distance traveled, and we guarantee that the minimum value of DeltaY is 0 and the maximum value is one mheaderheight, one of our custom properties.

This is to limit the distance, that is to say we can not infinitely pull down, pull down to a certain distance can not continue to pull.


In addition, you may think of a slippery situation, there are two types of sliding, one is in the initial position on the slide.

At this time Headerview should not move, but to ScrollView to deal with this event (because this is the sliding effect of ScrollView itself Ah!). )

So we have to use getscrolly () ==0 to determine whether the initial position on the beginning of the slide, if it is, do not implement drag

So we can see the judgment in the down and use Mislayout to make this mark.

If you are not currently scrolling from the initialization position, do not let the user drag            if (getscrolly () = = 0) {                mislayout = true;            }

In addition, it is to pull a distance, and then slide, then the processing with the pull is the same, as long as the call layout to modify the location of the good

can see that if

if (DeltaY > 0 && deltay >= getscrolly () && getscrolly () = = 0) {                ontouchevent (event);                return true;            }
Control is in its initial position, and it pulls down, we call ScrollView's Ontouchevent () method directly and intercept the event


I'll see what the Ontouchevent () method did, first look at the move.

@Override public boolean ontouchevent (Motionevent event) {switch (event.getaction ()) {case Motionev Ent.                Action_move: {int deltay = (int) event.gety ()-mtouchpoint.y; DeltaY = DeltaY < 0?                0: (DeltaY > Mheaderheight mheaderheight:deltay);  if (DeltaY > 0 && deltay >= getscrolly () && mislayout) {Float headermoveheight =                    DeltaY * 0.5f * scroll_ratio;                    Mheadercurtop = (int) (mheadinitrect.top + headermoveheight);                    Mheadercurbottom = (int) (Mheadinitrect.bottom + headermoveheight);                    float contentmoveheight = DeltaY * scroll_ratio;                    Mcontenttop = (int) (mcontentinitrect.top + contentmoveheight);                    Mcontentbottom = (int) (Mcontentinitrect.bottom + contentmoveheight); if (mcontenttop <= mheadercurbottom) {mheaderview.layout (mheadinitrect.left, Mheadercurtop, Mheadinitrect.right, Mheadercurbottom);                        Mcontentview.layout (Mcontentinitrect.left, Mcontenttop, Mcontentinitrect.right, Mcontentbottom);                        Mismoving = true;                    Menablemoving = true; }}} break;

As you can see, we get the distance of the move, and after the restriction, we calculate the new position based on the initial coordinates of the Headerview,contentview, and then call the layout () method.

Here's a scroll_ratio we call damping coefficients to provide a pull-down effect (that is, we can move as much as we pull down)

and another judgment.

if (mcontenttop <= mheadercurbottom) {<span style= "font-family:arial, Helvetica, Sans-serif;" ...} </span>
That means the top of the Contentview, not more than the bottom of headerview, otherwise the two will be separated! Because Contentview moved relatively slowly, so headerview, Contentview still can continue to pull down, so at this point can not exceed the bottom of headerview.


Ok, so far, the drop-down effect is basically implemented.

Pull-down to see the rebound effect, which obviously needs to be handled in the UP event

@Override public boolean ontouchevent (Motionevent event) {switch (event.getaction ()) {case Motionev Ent.            Action_move: {...}            Break Case MOTIONEVENT.ACTION_UP: {//Bounce if (mismoving) {mheaderview.layout (MH                    Eadinitrect.left, Mheadinitrect.top, Mheadinitrect.right, Mheadinitrect.bottom);                    Translateanimation Headanim = new Translateanimation (0, 0, mheadercurtop-mheadinitrect.top, 0);                    Headanim.setduration (200);                    Mheaderview.startanimation (Headanim); Mcontentview.layout (Mcontentinitrect.left, Mcontentinitrect.top, Mcontentinitrect.right, MContentInitRect.bottom)                    ;                    Translateanimation Contentanim = new Translateanimation (0, 0, mcontenttop-mcontentinitrect.top, 0);                    Contentanim.setduration (200);        Mcontentview.startanimation (Contentanim);            Mismoving = false;                } menablemoving = false;            Mislayout = false;        } break;        }//Disables sliding of the control itself. If Menablemoving returns True, then the Super.ontouchevent (event) will not be executed//only if it returns false, Super.ontouchevent (event)/ /prevents the control itself from sliding, it will let it, the original should be sliding will not slide, such as scrolling up return menablemoving | |    Super.ontouchevent (event); }

I remember setting the mismoving this flag to true when I pulled it down, we've pulled it down, we're only going to pull the animation down, and in any other action_up, we shouldn't call the animation.

Yes, Springback is achieved using animation translateanimation.

The more confusing is that

Mheaderview.layout (Mheadinitrect.left, Mheadinitrect.top, Mheadinitrect.right, Mheadinitrect.bottom);
What is the purpose of this sentence? I restored the headerview to the initial position and then called the animation.

For the coordinate calculation of the animation, we need to know that the origin is the current position of the view, that is, even if the view is pulled down, when using animation, the current position is calculated as the starting point.

Now we restore the view position, then we set the goal in translateanimation y coordinate is 0, in fact, the y-coordinate, is the current position minus the initial position.

In this way, the moving distance of the animation can be calculated reasonably.

Some people will say, this will not splash screen it? The answer is no, because OnLayout is done in a flash, so the human eye is not feeling the change.

The so-called rebound is to let the control in a certain period of time to continue to restore the initial position.


Finally, let's take a look at the return value of Ontouchevent (Motionevent event),

Suppresses the sliding of the control itself.        If Menablemoving returns True, then it will not execute Super.ontouchevent (event)        //only if it returns false Super.ontouchevent (event)        //Prevent the control itself from sliding, it will let it, the original should be sliding will not slide, such as scrolling up        return menablemoving | | super.ontouchevent (event);
The note is clear, which is used to prohibit the scrollview itself from sliding, when the drag effect, the menablemoving set to True, so Super.ontouchevent (event), will not be called, That is, the effect of ScrollView is forbidden.

Also in action_up we can see that to set Menablemoving to False, resume its swipe function.

Finally, I hope you understand my explanation, if you do not understand, you can read the original author's blog.

As for the specific implementation of GitHub's Pullscrollview, we can look at the comments I wrote, in fact, the idea is the same, but more complex to achieve.

Resource address is: http://download.csdn.net/detail/kangaroo835127729/8945515

Finally, post the complete source code for this article Pullscrollview

/** * Created by Harvic on 2015/6/17 * @adress blog.csdn.net/harvic880925 */public class Pullscrollview extends ScrollView    {//Bottom picture view private View Mheaderview;    The initial position of the head picture is private rect mheadinitrect = new rect ();    Bottom view private view mcontentview;    The initialization position of the contentview of the ScrollView private Rect mcontentinitrect = new rect ();    Initial click location private point mtouchpoint = new points ();    Identifies whether the current view moves Boolean mismoving = false;    Whether to suppress the movement of the control itself Boolean menablemoving = false;    Whether to use the layout function to move the layouts Boolean mislayout = false;     /** * Damping coefficient, the smaller the resistance is greater.    */private static final float scroll_ratio = 0.5f;    private int mcontenttop, Mcontentbottom;    private int mheadercurtop, Mheadercurbottom;    User-defined Headview height private int mheaderheight = 0;        Public Pullscrollview (context context, AttributeSet Attrs) {Super (context, attrs);    Init (context, attrs); } public Pullscrollview (context context, AttributeSet attrs, int defstyleattr) {        Super (context, attrs, defstyleattr);    Init (context, attrs); } private void init (context context, AttributeSet attrs) {//Set scroll mode Setoverscrollmode (over_scro        Ll_never);            if (null! = attrs) {TypedArray ta = context.obtainstyledattributes (attrs, R.styleable.pullscrollview);                if (ta! = null) {mheaderheight = (int) ta.getdimension (r.styleable.pullscrollview_headerheight,-1);            Ta.recycle ();    }}} public void Setmheaderview (view view) {Mheaderview = view; } @Override protected void Onfinishinflate () {if (Getchildcount () > 0) {mcontentview = Getchi        Ldat (0);    } super.onfinishinflate (); } @Override public boolean ontouchevent (Motionevent event) {switch (event.getaction ()) {case Mot Ionevent.action_move: {int deltay = (int) event.gety ()-mtouchpoint.y;//DeltaY =DeltaY < 0?                0: (DeltaY > Mheaderview.getheight () mheaderview.getheight (): DeltaY); DeltaY = DeltaY < 0?                0: (DeltaY > Mheaderheight mheaderheight:deltay);  if (DeltaY > 0 && deltay >= getscrolly () && mislayout) {Float headermoveheight =                    DeltaY * 0.5f * scroll_ratio;                    Mheadercurtop = (int) (mheadinitrect.top + headermoveheight);                    Mheadercurbottom = (int) (Mheadinitrect.bottom + headermoveheight);                    float contentmoveheight = DeltaY * scroll_ratio;                    Mcontenttop = (int) (mcontentinitrect.top + contentmoveheight);                    Mcontentbottom = (int) (Mcontentinitrect.bottom + contentmoveheight); if (mcontenttop <= mheadercurbottom) {mheaderview.layout (Mheadinitrect.left, Mheadercurtop, mHe                        Adinitrect.right, Mheadercurbottom); Mcontentview.layout (McontentiniTrect.left, Mcontenttop, Mcontentinitrect.right, Mcontentbottom);                        Mismoving = true;                    Menablemoving = true;            }}} break; Case MOTIONEVENT.ACTION_UP: {//Bounce if (mismoving) {mheaderview.layout (MH                    Eadinitrect.left, Mheadinitrect.top, Mheadinitrect.right, Mheadinitrect.bottom);                    Translateanimation Headanim = new Translateanimation (0, 0, mheadercurtop-mheadinitrect.top, 0);                    Headanim.setduration (200);                    Mheaderview.startanimation (Headanim); Mcontentview.layout (Mcontentinitrect.left, Mcontentinitrect.top, Mcontentinitrect.right, MContentInitRect.bottom)                    ;                    Translateanimation Contentanim = new Translateanimation (0, 0, mcontenttop-mcontentinitrect.top, 0);                    Contentanim.setduration (200); Mcontentview.startanimation (Contentanim);                Mismoving = false;                } menablemoving = false;            Mislayout = false;        } break;        }//Disables sliding of the control itself. If Menablemoving returns True, then the Super.ontouchevent (event) will not be executed//only if it returns false, Super.ontouchevent (event)/ /prevents the control itself from sliding, it will let it, the original should be sliding will not slide, such as scrolling up return menablemoving | |    Super.ontouchevent (event); } @Override public boolean onintercepttouchevent (Motionevent event) {if (event.getaction () = = Motionevent.ac            Tion_down) {//Save original position mtouchpoint.set ((int) event.getx (), (int) event.gety ());            Mheadinitrect.set (Mheaderview.getleft (), Mheaderview.gettop (), Mheaderview.getright (), Mheaderview.getbottom ()); Mcontentinitrect.set (Mcontentview.getleft (), Mcontentview.gettop (), Mcontentview.getright (),            Mcontentview.getbottom ());            Mismoving = false; If you are not currently scrolling from the initialization position, do not let the user drag the IF (GEtscrolly () = = 0) {mislayout = true; }} else if (event.getaction () = = Motionevent.action_move) {//If the current event is an event we want to handle, such as the drop-down now, we cannot let the child control Rationale for this event//you need to intercept it here, not to the child control, not to let the child control consume this event//otherwise the behavior of the child control may conflict with ours int deltay = (int) event.gety ()-mtouchpoint.y;//DeltaY = DeltaY < 0?            0: (DeltaY > Mheaderview.getheight () mheaderview.getheight (): DeltaY); DeltaY = DeltaY < 0?            0: (DeltaY > Mheaderheight mheaderheight:deltay);                if (DeltaY > 0 && deltay >= getscrolly () && getscrolly () = = 0) {ontouchevent (event);            return true;    }} return Super.onintercepttouchevent (event); }}


Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

Pullscrollview Source Code 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.