PullScrollView source code parsing, pullscrollview source code

Source: Internet
Author: User

PullScrollView source code parsing, pullscrollview source code

PullScrollView is an open source project on Github, which is mainly used to achieve the effect of header scaling when the drop-down is implemented. For the project address, see https://github.com/MarkMjw/PullScrollView.

Let's take a look at the implementation (Graphic intrusion and deletion)


However, because the code of this project is complex, but it can be achieved without doing so, I found another article on the Internet about pullScrollView, which is very well written, this article is just based on the article http://blog.csdn.net/harvic880925/article/details/46728247 to talk about their own understanding and gains, if you want to know more details, you can visit this article.


The following describes the implementation idea. The overall layout is a ScrollView, which means that we need to inherit ScrollView to implement our own control pullScrollView. For images with a pull-down effect, it is actually a layout other than ScrollView. pullScrollView provides a setHeader () method to set the drop-down header for itself.

We can set any View as the head of the pullScrollView. When the pullScrollView is dragged, we can change the layout of this View. We call this View HeaderView.

So how does it change? The layout () method is used to change the position of the HeaderView. When you drag the HeaderView, the position of the HeaderView is constantly modified to move down. However, this only achieves the drag effect. To achieve a similar stretch effect, we need to change the marginTop of HeaderView to a negative number, so that when the position is moved, the HeaderView will be stretched.

The better result is that the bottom will also be stretched, so it looks like the two sides are pulled together. In the figure, we can see the subject layout under HeaderView, which is called ContentView. We make ContentView mask part of HeaderView, and then make the pull-down growth speed of ContentView and HeaderView different, in this way, the bottom is also stretched.


Now let's think about how to use layout ()? Obviously, we need to listen to the down and move events of our fingers, record the next position when we are down, and subtract the down position from the current position when moving. This difference is the increasing distance of the HeaderView position, as for ContentView, We can multiply it by a decimal number to make it smaller.

Theoretically, we have implemented the pull-down effect, but there are many defects. Now let's look at the source code first.

Attribute and constructor

Public class PullScrollView extends ScrollView {// bottom Image View private View mHeaderView; // header image initialization position private Rect mHeadInitRect = new Rect (); // bottom View private View mContentView; // The initial position of contentView in ScrollView is private Rect mContentInitRect = new Rect (); // The initial position is private Point mTouchPoint = new Point (); // identify whether the current view is moved boolean mIsMoving = false; // whether to prohibit the movement of the control boolean mEnableMoving = false; // whether to use layout Function movement layout boolean mIsLayout = false;/*** damping factor, the smaller the resistance, the larger. */private static final float SCROLL_RATIO = 0.5f; private int mContentTop, mContentBottom; private int mHeaderCurTop, mHeaderCurBottom; // The Custom headview height is 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_SCROLL_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 attributes, you can refer to the comments. We initialize two Rect and one Point. The init () method actually obtains two custom attributes.

Note that the position of the ScrollView remains unchanged, but the contentView in the ScrollView changes. Therefore, we use setOverScrollMode (OVER_SCROLL_NEVER) to restrict its movement.

Then, according to the above, we need to have a setHeader () method.

public void setmHeaderView(View view) {        mHeaderView = view;    }

In addition, we can also obtain the contentView to facilitate modification of its location, which must be obtained after layout rendering.

@Override    protected void onFinishInflate() {        if (getChildCount() > 0) {            mContentView = getChildAt(0);        }        super.onFinishInflate();    }


Next, listen to the event.

@ Override public boolean onInterceptTouchEvent (MotionEvent event) {if (event. getAction () = MotionEvent. ACTION_DOWN) {// Save the original location 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 (), mContentV Iew. getBottom (); mIsMoving = false; // if the current rolling is not started from the initialization position, you are not allowed to drag if (getScrollY () = 0) {mIsLayout = true ;}} else if (event. getAction () = MotionEvent. ACTION_MOVE) {// if the current event is the event we want to handle, such as the current drop-down, at this time, we cannot let the sub-control handle this event // We need to intercept it here and not pass it to the sub-control, do not let the child control consume this event // otherwise, the behavior of the Child control may conflict with our 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 people may wonder why they listen in the onInterceptTouchEvent (MotionEvent) method, rather than in the onTouchEvent (MotionEvent?

This actually involves an issue of event interception and transfer mechanisms. First, we need to understand that for the GroupView of ScrollView, when we press the finger, we first call the dispatchTouchEvent () method, this method is used to distribute Touch events. By default, onInterceptTouchEvent (MotionEvent event) is called ). This method is used for event interception. If true is returned, the event will not be passed down, that is, it will not be distributed to the Child control.

If false is returned, it is distributed to the Child control, which is processed by the onTouchEvent () method of the Child control. If the onTouchEvent () method of the Child control returns false, then, the event is distributed to the onTouchEvent () method of ScrollView for processing. If true is returned, the onTouchEvent () method sent to ScrollView is not considered.

That is to say, the distribution order of Touch events is as follows:

OnInterceptTouchEvent (MotionEvent event) of the parent control --- onTouchEvent () of the Child control -- nTouchEvent () of the parent Control ()

If any of the above transfer processes returns true, the distribution will not continue.


OK. Now let's understand the above method. We get the down position in the onInterceptTouchEvent (MotionEvent event) method to avoid the subcontrol from consuming the event (as the author of pullScrollView, we cannot ensure that the user's sub-controls will not be consumed ).


In addition, when we go down, we record the finger position, which is recorded using mTouchPoint.

Record the initial position of the headerView, which is recorded using mHeadInitRect.

Records the initial position of contentView, which is recorded by mContentInitRect.

We do not care about other settings for the moment.


Next, when we look at move,

int deltaY = (int) event.getY() - mTouchPoint.y;deltaY = deltaY < 0 ? 0 : (deltaY > mHeaderHeight ? mHeaderHeight : deltaY);
Note that we have processed the moving distance. We ensure that the minimum value of deltaY is 0, and the maximum value is an mHeaderHeight, which is a custom attribute.

This is to limit the drop-down distance. That is to say, we cannot pull the drop-down without limit.


In addition, you may think of two ways to slide. One is to slide at the initial position.

At this time, headerView should not be moved, but should be handled by ScrollView (because this is the sliding effect of ScrollView itself !)

Therefore, we need to use getScrollY () = 0 to determine whether the slide starts at the initial position. If yes, drag and drop is not implemented.

So we can see the judgment in the down, using mIsLayout for this mark

// If the current rolling is not started from the initialization position, you will not be allowed to drag if (getScrollY () = 0) {mIsLayout = true ;}

In addition, there is a pull-down distance, and then slide up. This is the same as the pull-down process. You only need to call layout to modify the location.

You can see that if

if (deltaY > 0 && deltaY >= getScrollY()&& getScrollY() == 0) {                onTouchEvent(event);                return true;            }
The controls are all in the initial position and drop-down. We can directly call the onTouchEvent () method of ScrollView and intercept events.


Let's take a look at what the onTouchEvent () method has done. Let's first look at the moving situation.

@Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.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;

We can see that we have obtained the moving distance again. After the restriction, we can calculate the new location based on the initial coordinates of headerView and contentView, and then call the layout () method.

Here is a SCROLL_RATIO called damping factor, which aims to provide effects that are difficult to pull down (that is to say, we can move as much as we can't pull down)

Another judgment is required.

if (mContentTop <= mHeaderCurBottom) {<span style="font-family:Arial, Helvetica, sans-serif;">...}</span>
This means that the top of the contentView cannot exceed the bottom of the headerView. Otherwise, the two are separated! Because contentView is relatively slow, after headerView does not move, contentView can still be pulled down, so it cannot exceed the bottom of headerView.


OK, so far, the pull-down effect is basically achieved.

The drop-down list shows the rebound effect, which must be handled in the up event.

@ Override public boolean onTouchEvent (MotionEvent event) {switch (event. getAction () {case MotionEvent. ACTION_MOVE :{......} break; case MotionEvent. ACTION_UP: {// rebound if (mIsMoving) {mHeaderView. layout (mHeadInitRect. 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;} // do not slide the control itself. // This statement is very powerful. If mEnableMoving returns TRUE, super will not be executed. onTouchEvent (event) // super is executed only when FALSE is returned. onTouchEvent (event) // disables the sliding of the control itself, so that it will not slide as it is supposed to, such as rolling up return mEnableMoving | super. onTouchEvent (event );}

I still remember setting the "mIsMoving" flag to "true" in the drop-down list. This indicates that we have already pulled down. We can only call the animation after the drop-down. In any other action_up, we should not call animations.

Yes, the bounce is achieved using the animated TranslateAnimation.

What's 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 Coordinate Calculation of an animation, we need to know that the origin is the current position of the View. That is to say, even if the View is pulled down, the animation is calculated based on the current position.

Now we need to restore the position of the View first, so we set the target Y coordinate in TranslateAnimation to 0. In fact, the Y coordinate is the current position minus the initial position.

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

Some people will say that this will not flash the screen? The answer is no, because onlayout is completed in an instant, so the human eyes cannot feel this change.

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


Finally, let's take a look at the returned value of onTouchEvent (MotionEvent,

// Do not slide the control itself. // This statement is very powerful. If mEnableMoving returns TRUE, super will not be executed. onTouchEvent (event) // super is executed only when FALSE is returned. onTouchEvent (event) // disables the sliding of the control itself, so that it will not slide as it is supposed to, such as rolling up return mEnableMoving | super. onTouchEvent (event );
The note is very clear, that is, it is used to prohibit the sliding of the ScrollView itself. When dragging the effect, set mEnableMoving to true, so that super. onTouchEvent (event); is not called, that is, the effect of ScrollView is disabled.

In addition, we can see in Action_up that we want to set mEnableMoving to false to restore its slide function.

Finally, I hope you can understand my explanation. If you don't understand it, you can read the original author's blog.

As for the specific implementation of github's pullScrollView, you can take a look at the notes I wrote. In fact, the idea is the same, but the implementation is more complicated.

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

Finally, the complete source code of the pullScrollView in this article is posted.

/*** Created by harvic on 2015/6/17 * @ adress blog.csdn.net/harvic880925 */public class PullScrollView extends ScrollView {// The bottom picture View private View mHeaderView; // The initial position of the header image is private Rect mHeadInitRect = new Rect (); // The bottom View is private View mContentView; // The initial position of contentView in ScrollView is private Rect mContentInitRect = new Rect (); // The initial position is private Point mTouchPoint = new Point (); // It indicates whether the current view moves boolean m IsMoving = false; // whether to prohibit movement of the control's own boolean mEnableMoving = false; // whether to use the layout function to move the layout boolean mIsLayout = false;/*** damping factor, the smaller the resistance, the larger the resistance. */private static final float SCROLL_RATIO = 0.5f; private int mContentTop, mContentBottom; private int mHeaderCurTop, mHeaderCurBottom; // The Custom headview height is 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_SCROLL_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 = getChildAt (0);} super. onFinishInflate () ;}@ Override public boolean onTouchEvent (MotionEvent event) {switch (event. getAction () {case M OtionEvent. 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) (trim. top + headerMoveHeight); mHeaderCurBottom = (int) (mHeadInitRect. bottom + headerMoveHeight); float contentMoveHeight = deltaY * SCROLL_RATIO; mContentTop = (int) (mContentInitRect. top + contentMoveHeight); mContentBottom = (in T) (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; case MotionEvent. ACTION_UP: {// rebound if (mIsMoving) {mHeaderView. lay Out (mHeadInitRect. 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 = ne W TranslateAnimation (0, 0, mContentTop-mContentInitRect. top, 0); contentAnim. setDuration (200); mContentView. startAnimation (contentAnim); mIsMoving = false;} mEnableMoving = false; mIsLayout = false;} break;} // do not slide the control itself. // This statement is very powerful. If mEnableMoving returns TRUE, super will not be executed. onTouchEvent (event) // super is executed only when FALSE is returned. onTouchEvent (event) // disables the sliding of the control itself, so that it will not slide as it is supposed to, such as rolling up return mEnableMoving | supe R. onTouchEvent (event) ;}@ Override public boolean onInterceptTouchEvent (MotionEvent event) {if (event. getAction () = MotionEvent. ACTION_DOWN) {// Save the original location mTouchPoint. set (int) event. getX (), (int) event. getY (); mHeadInitRect. set (mHeaderView. getLeft (), mHeaderView. getTop (), mHeaderView. getRight (), mHeaderView. getBottom (); mContentInitRect. set (mContentView. getLeft (), mContentView. getTop (), mContent View. getRight (), mContentView. getBottom (); mIsMoving = false; // if the current rolling is not started from the initialization position, you are not allowed to drag if (getScrollY () = 0) {mIsLayout = true ;}} else if (event. getAction () = MotionEvent. ACTION_MOVE) {// if the current event is the event we want to handle, such as the current drop-down, at this time, we cannot let the sub-control handle this event // We need to intercept it here and not pass it to the sub-control, do not let the child control consume this event // otherwise, the behavior of the Child control may conflict with our 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 Disclaimer: This article is an original article by the blogger and cannot be reproduced without the permission of the blogger.

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.