Viscous Sliding - Custom Stickiness View

Keywords: github Android Mobile

With the emergence of design package, the android interface has undergone tremendous changes and the effects of various sliding combinations. Now I will customize one of the viscous sliding. The results are as follows:

You see the effect, here I inherited Liner Layout, a little more convenient, if it was ViewGroup, it would be a little more complicated. There are three parts:
1.head1, the top movable Layout.
Head 2, fixed head, does not slide except the screen.
3. Sliding Layout (here it can only be ListView, but it can also be any Sliding View, as long as the header is given a sliding time)

The difficulty with this Stickiness View is to resolve the sliding conflicts and the interception of events, and I'll come along next.
First, to determine when HeadLayout can intercept events, you need to determine when ListView reaches the top and bottom.

 @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        View v = mListView.getChildAt(0);
        //When the top of first Item is zero, it is assumed that it has reached the top of ListView.
        if (mListView.getChildCount() > 0 && firstVisibleItem == 0) {
            //Slide to the top
            if (v.getTop() == 0) {
                //Slide to the top
                isListViewTop = true;
            } else {
                isListViewBottom = false;
            }
        }else if (mListView.getChildCount()>0&&firstVisibleItem+visibleItemCount==totalItemCount){
            final View bottomChildView = mListView.getChildAt(mListView.getChildCount()-1);
//When the bottom of the last itemView >= the height of the ListView, you think you've reached the bottom.
            if             (mListView.getHeight()>=bottomChildView.getBottom()){
                isListViewBottom = true;
            }else {
                isListViewBottom = false;
            }
        }else {
            isListViewBottom = false;
            isListViewTop = false;
        }

The reason is simple, because the getTop and getBottom methods of View are relative to the location of the parent container. If you are familiar with the Layout method, you will understand it very well.

2. Knowing the time when HeadView intercepts events, we need to find out when we can intercept clicks and slide on this basis.

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchY = ev.getRawY();
                isIntercept = false;
                break;
            case MotionEvent.ACTION_MOVE:
                float distant = ev.getRawY() - touchY;
                if (isListViewTop) {
                    switch (mHeadPosition) {
                        case TOP:
                            if (distant > 0) isIntercept = true;
                            break;
                        case CENTER:
                            isIntercept = true;
                            break;
                    }
                }
                if (isListViewBottom){
                    switch (mHeadPosition) {
                        case CENTER:
                            isIntercept = true;
                            break;
                        case BOTTOM:
                            if (distant < 0) isIntercept = true;
                            break;
                    }
                }

                break;
            case MotionEvent.ACTION_UP:
                isIntercept = true;
                break;
        }
        return isIntercept;
    }

Let's talk about the onInterceptTouchEvent (Motion Event ev). This method will be called first. When an event sequence is intercepted once, the subsequent event action of this event will not call this method again. That is to say, when the ViewGroup decides to intercept an event, it is destined to consume subsequent event action. Here is the location status of the HeadView.

    public static final int TOP = 0;//Contraction state
    public static final int CENTER = 1;//Intermediate state
    public static final int BOTTOM = 2;//Deployment state

As for the details, I think you can draw a picture. One thing to note is that when intercepting the sequence of events, the ACTION_DOWN event can not be intercepted, because if interception is meaningless, subsequent events can not be controlled, and it is impossible to continue to pass the sequence of events to ChildView.

3. Mobile HeadView.

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //Unavailable
            break;
        case MotionEvent.ACTION_MOVE:
            int distant = (int) (touchY - event.getRawY());
            if (getScrollY() +  distant-1 < MAXY && getScrollY() +  distant > 0) {
                scrollTo(0, getScrollY() +  distant);
            }
            break;
        case MotionEvent.ACTION_UP:
            if (getScrollY() == 0) mHeadPosition = BOTTOM;
            if (getScrollY() == MAXY) mHeadPosition = TOP;
            if (getScrollY() > 0 && getScrollY() < MAXY) mHeadPosition = CENTER;
            if (getScrollY() > MAXY / 2) {
                mScroll.startScroll(0, getScrollY(), 0, MAXY-getScrollY(),100);
                invalidate();
                mHeadPosition = TOP;
            }
            if (getScrollY() < MAXY / 2) {
                mScroll.startScroll(0, getScrollY(),0,-getScrollY(),100);
                invalidate();
                mHeadPosition = BOTTOM;
            }
            break;
    }
    return super.onTouchEvent(event);
}

Here, in order to make sliding smoothly with home, I use Scroller class, which is a tool class specially dealing with elastic sliding. First, I initialize the constructor and call the startScroll() method (four parameters: sliding x, sliding y, sliding x offset, sliding y offset), then refresh the view, and finally rewrite the computeScroll() method.

@Override
public void computeScroll() {
    super.computeScroll();
    if (mScroll.computeScrollOffset()){
        scrollTo(mScroll.getCurrX(),mScroll.getCurrY());
        postInvalidate();
    }
}

Okay, basically done. We still need to get the height of the HeadView in the first time, so it's better to get it in onMeasure (), and only get it once as follows

 if (MAXY == -1)
        MAXY = mHeadSecond.getMeasuredHeight();

In the onFinishInflate() method, the execution of this method indicates that all View s have been add ed, and it is appropriate for us to initialize them here.

 @Override
        protected void onFinishInflate() {
        super.onFinishInflate();
        int count = getChildCount();
        //This sticky layout only supports ListView
        if (count == 3 && getChildAt(2) instanceof ListView)
            init();
    } 
  /**
     * Initialization
     */
    private void init() {
        //Get child elements
        mHeadFiest = getChildAt(0);
        mHeadSecond = getChildAt(1);
        mListView = (ListView) getChildAt(2);
        mListView.setOnScrollListener(this);
        mScroll = new Scroller(getContext());
    }

Okay, that's basically all. Good night, everybody! ___________
GitHub address: https://github.com/yzzAndroid/LianXinView

Posted by ComputerChip on Wed, 10 Apr 2019 22:51:32 -0700