Custom behavior – perfect imitation QQ browser home page, the US group business details page

Use CoordinatorLayout create a variety of cool effects

Custom Behavior – imitation almost known, FloatActionButton hiding and showing

NestedScrolling mechanism Insights

Step by step take you read CoordinatorLayout source

Custom Behavior – Imitation Sina microblogging found to achieve page

ViewPager, ScrollView nested ViewPager slide conflict resolution

Custom behavior – perfect imitation QQ browser home page, the US group business details page

Foreword

I remember two years ago when the custom behavior has written articles Custom Behavior – Imitation Sina microblogging found to achieve page, and now there are almost more than ten thousand of the amount of reading it.

Today, the upgrade behavior, behavior with respect to two years ago, adds the following features

    Cascade callback listener increases during the sliding, the sliding distance of outer convenient, corresponding animation, show cool the UI, provided by a callback listener setPagerStateListener

    To the top of the slide when the slide is able to be provided to slide down Head, method setCouldScroollOpen

    Finger sliding header portion of inertia when the increase fling callback, according to need, whether part of the slide list content, method setOnHeaderFlingListener

    HeaderBehavior, ContentBehavior code optimization, business logic peeled apart to facilitate reuse.


Instructions for use

Renderings

We first look at Sina Weibo found that the effect of the page:

Next we look at two years ago, we modeled the effect of Sina Weibo implemented in view

Imitation QQ browser

Imitation US group business details page:

Analysis shows:

There are two states, open and close state.

    It refers to the open state when the Tab + ViewPager yet when slid to the top, header has not been completely removed screen

    Tab + ViewPager close sliding state refers to when the top, Header when the screen is removed

From the renderings, we can see that in the open state, we slide up ViewPager inside RecyclerView time, RecyclerView does not move up (RecyclerView slip event to the outside of the container processing, it has been all consumed a), and the entire layout (refer Header + Tab + ViewPager) will be shifted upward. When Tab to the top of the slide, we slide up ViewPager inside RecyclerView when RecyclerView can slide up properly, that at this time no interceptions slip events outside of the container.

At the same time we can see in the open state, we do not support the drop-down refresh, this is easier to implement, monitor status page, if the state is open, we SwipeRefreshLayout setEnabled set to false, so as not to intercept the event, close the page when SwipeRefreshLayout setEnabled set to TRUE, so you can refresh the drop-down support.

Based on the above analysis, we can put this whole effect divided into three parts

Header portion of the first portion: the Header portion has not slid to the top of the time (i.e., open time), followed swipe
    The second part Content part: when we slip up, when the Header in the open state, this time sliding up Header, recyclerView content will not be part of the slide, when the header is in close state, content portion of the slide up, slide up RecyclerView. When we slide down, header and not with the sliding, sliding recyclerView content only part of
    The third part of the search part: when we slip up, Search with the sliding part will ultimately remain in a fixed position.

We define the relationship between these three parts is dependent on the Content Header. Header when moving, Content followed the move. So when we deal with the sliding events, just to properly handle the Behavior Header section of the oK, Behavior Content section of the need to deal slip event, just depends on the Header, you can follow the movement accordingly. behavior Search section need not be processed slip event, and simply rely Header, moving along accordingly.

As for how to achieve specific, you can see the custom Behavior – Imitation Sina microblogging found to achieve page, the core idea is similar to not repeat here.

Instructions for use

Here we have like QQ browser demo explained:

Together we look at how to use: simply, just two steps:

    The first step, respectively, in the xml file for the header part, content specified part of our corresponding behavior

    A second portion disposed inside the code number of configuration parameters

The first step: writing xml file, and specify the appropriate behavior




    
    


        

            


        
    

    
    

        

        

        
    

    
    

        

        

        

    



Step Two: dynamically set some parameters in the code which

private void initBehavior() {
    Resources resources = DemoApplication.getAppContext().getResources();
    mHeaderBehavior = (QQBrowserHeaderBehavior) ((CoordinatorLayout.LayoutParams) findViewById(R.id.id_uc_news_header_pager).getLayoutParams()).getBehavior();
    mHeaderBehavior.setPagerStateListener(new QQBrowserHeaderBehavior.OnPagerStateListener() {
        @Override
        public void onPagerClosed() {
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "onPagerClosed: ");
            }
            Snackbar.make(mNewsPager, "pager closed", Snackbar.LENGTH_SHORT).show();
            setFragmentRefreshEnabled(true);
            setViewPagerScrollEnable(mNewsPager, true);
        }

        @Override
        public void onScrollChange(boolean isUp, int dy, int type) {

        }

        @Override
        public void onPagerOpened() {
            Snackbar.make(mNewsPager, "pager opened", Snackbar.LENGTH_SHORT).show();
            setFragmentRefreshEnabled(false);
        }
    });
    // 设置为 header height 的相反数
    mHeaderBehavior.setHeaderOffsetRange(-resources.getDimensionPixelOffset(R.dimen.header_height));
    // 设置 header close 的时候是否能够通过滑动打开
    mHeaderBehavior.setCouldScroollOpen(false);
    
    mContentBehavior = (QQBrowserContentBehavior) ((CoordinatorLayout.LayoutParams) findViewById(R.id.behavior_content).getLayoutParams()).getBehavior();
    // 设置依赖于哪一个 id,这里要设置为 Header layout id
    mContentBehavior.setDependsLayoutId(R.id.id_uc_news_header_pager);
    // 设置 content 部分最终停留的位置
    mContentBehavior.setFinalY(resources.getDimensionPixelOffset(R.dimen.header_title_height));
}

mHeaderBehavior.setHeaderOffsetRange disposed offset Header section, we achieved by translationY, we generally set to the opposite of the height of the header.
    mHeaderBehavior.setCouldScroollOpen (false), if the time set header close can be opened by sliding.

mContentBehavior.setDependsLayoutId (R.id.id_uc_news_header_pager); depending on which is provided a id, set here to Header layout id. mContentBehavior.setFinalY part of the final set position content to stay.

We look OnPagerStateListener callback

/**
 * callback for HeaderPager 's state
 */
public interface OnPagerStateListener {
    /**
     * do callback when pager closed
     */
    void onPagerClosed();

    /**
     * when scrooll, it would call back
     *
     * @param isUp  isScroollUp
     * @param dy   child.getTanslationY
     * @param type touch or not touch, TYPE_TOUCH, TYPE_NON_TOUCH
     */
    void onScrollChange(boolean isUp, int dy, @ViewCompat.NestedScrollType int type);

    /**
     * do callback when pager opened
     */
    void onPagerOpened();
}

There are three main methods, the first method, onPagerClosed Close the time when the header will callback, the second method, when the header slide distance change will onScrollChange callback method. It has three parameters, whether isUp representatives are slid upward, dy denotes an offset of the header, is a type representative of the type of touch or non-touch (i.e., sliding fling)

If you want to do some cool effects, you can onScrollChange method, according to the sliding distance of each different View animation accordingly.

Imitation Mito business details page

Step with the above steps like QQ browser is the same, the same steps are not repeated here, said several key points:
    First: in the page header close, we can open by sliding header, this is done by calling mHeaderBehavior.setCouldScroollOpen (true); to achieve.
    Second: sliding header, fling, they can see recyclerView content is also part of the slide, we are through fling the event header do, slide the manual call RecyclerView of smoothScrollBy onFlingStart time.

mHeaderBehavior.setOnHeaderFlingListener(new HeaderFlingRunnable.OnHeaderFlingListener() {
    @Override
    public void onFlingFinish() {

    }

    @Override
    public void onFlingStart(View child, View target, float velocityX, float velocityY) {
        Log.i(TAG, "onFlingStart: velocityY =" + velocityY);
        if (velocityY < 0) {
            mRecyclerView.smoothScrollBy(0, (int) Math.abs(velocityY), new AccelerateDecelerateInterpolator());
        }

    }

    @Override
    public void onHeaderClose() {

    }

    @Override
    public void onHeaderOpen() {

    }
});

Encountered pit

header section can not respond to slip events

We are a custom NestedLinearLayout, rewrite its onTouchEvent event, the event delivery mechanism by NestedScrolling to NestedScrollingParent, namely CoordinatorLayout, and will give NestedScrollingParent child View of behavior for processing.

@Override
public boolean onTouchEvent(MotionEvent event) {
    mGestureDetector.onTouchEvent(event);
    final int action = MotionEventCompat.getActionMasked(event);
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL
                    | ViewCompat.SCROLL_AXIS_VERTICAL);

            break;
        case MotionEvent.ACTION_MOVE:
            int dy = (int) (event.getRawY() - lastY);
            lastY = (int) event.getRawY();
            //  dy < 0 上滑, dy>0 下拉
            if (dy < 0) { // 上滑的时候交给父类去处理
                if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) // 如果找到了支持嵌套滚动的父类
                        && dispatchNestedPreScroll(0, -dy, consumed, offset)) {//
                    // 父类进行了一部分滚动

                }
            } else {
                if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) // 如果找到了支持嵌套滚动的父类
                        && dispatchNestedScroll(0, 0, 0, -dy, offset)) {//
                    // 父类进行了一部分滚动

                }
            }
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            stopNestedScroll();
            break;

    }
    return super.onTouchEvent(event);
}

When we set up a click event to the child View header when not slide header

Android has some understanding of the event distribution mechanism, all know, the Android, the default event delivery mechanism like this,

When TouchEvent occurs, the first Activity TouchEvent passed to the topmost View, TouchEvent dispatchTouchEvent first reaches the top level view, and then distributed by dispatchTouchEvent method.

    Returns true if dispatchTouchEvent consumer event, the end of the event.

  • If dispatchTouchEvent returns false, then passed back to the parent View onTouchEvent event processing;

    OnTouchEvent event returns true, the event ended, it returns false, to the parent View of OnTouchEvent processing method

  • If dispatchTouchEvent return to super, the default method is invoked their onInterceptTouchEvent

    By default interceptTouchEvent call back method super, super default method returns false, it will be handed over to child View onDispatchTouchEvent method of treatment
                If interceptTouchEvent returns true, which is intercepted off, then handed it to deal with the onTouchEvent
                If interceptTouchEvent returns false, then passed to the child view, the view from the dispatchTouchEvent child again began distributing this event.

So when we set up a sub-View click event, because of the default parent does not intercept events, will come onToucheEvent child View events in, since the click event, the event is consumed, they will not callback in the parent View onTouchEvent the ACTION_MOVE event.

The solution: rewrite onInterceptToucheEvent event NestedLinearLayout of when is ACTION_MOVE event returns true, interception, it will call its own onTouchEvent event, to ensure you can slide.

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mDownY = (int) event.getRawY();
            // 当开始滑动的时候,告诉父view
            startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL
                    | ViewCompat.SCROLL_AXIS_VERTICAL);
            break;
        case MotionEvent.ACTION_MOVE:
            // 确保不消耗 ACTION_DOWN 事件
            if (Math.abs(event.getRawY() - mDownY) > mScaledTouchSlop) {
                logD("onInterceptTouchEvent: ACTION_MOVE  mScaledTouchSlop =" + mScaledTouchSlop);
                return true;
            }
    }
    return super.onInterceptTouchEvent(event);
}

But here there is a pit, a normal click event will trigger ACTION_DOWN, ACTION_MOVE, ACTION_UP, returns true if we directly ACTION_MOVE which will result in a sub-View event onClick failure.

Solution:

final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mScaledTouchSlop = configuration.getScaledTouchSlop();
if (Math.abs(event.getRawY() - mDownY) > mScaledTouchSlop) {
    return true;
}

About slide conflict resolution, you can read my previous blog post: ViewPager, ScrollView nested ViewPager slide conflict resolution

How to determine the header is fling action

We are here to do through gestures processor GestureDetector, of course, you can also be calculated by VelocityTracker, but more complicated

public boolean onTouchEvent(MotionEvent event) {
    mGestureDetector.onTouchEvent(event);
}

        GestureDetector.OnGestureListener onGestureListener = new GestureDetector.OnGestureListener() {

            @Override
            public boolean onDown(MotionEvent e) {
                return false;
            }

            -----// 省略若干代码

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                Log.d(TAG, "onFling: velocityY =" + velocityY);
//                fling((int) velocityY);
                getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
                return false;
            }
        };

        mGestureDetector = new GestureDetector(getContext(), onGestureListener);



Digression

Sometimes, I make some notes is really quite important.

This time writing this blog post, because to do a similar effect in the project. At first, nothing really thinking. But clearly we have to remember that two years ago, wrote a similar article, the specific implementation principle has long been forgotten. Two years ago I looked at the blog, organize his thoughts, the code will be moved to the project, we found some pit. Tinkering, the pits are filled.

Imagine if the principle had not been recorded, the effect is really quite hard to achieve. If you Coordinatorlayout, behavior, NestedScroll these mechanisms are not familiar with, you simply can not achieve. Two years ago to write a custom Behavior - when imitation Weibo pages found to achieve this blog, private letter received a lot of feedback saying that they do have some effect did this over two weeks still can not be achieved, I am very grateful write this blog. Therefore, from now on, try a do my notes. Really, a good memory as bad written.

The second point is deeper feelings, beginning, I read the code I wrote two years ago, I started a reaction, I went to, what is this garbage code. Indeed, in many places that quite bad, behavior coupled business logic, is difficult to reuse, not good maintenance. So this time, I pulled out the behavior in his spare time out, they wanted to achieve a similar effect, easy, biu biu biu.

So much, are summarized below

    The notes do not meet, particularly with regard to the principle of

    The code must be awe, not much to say, take his insight

    Keep a humble heart


CoordinatorLayoutExample

Feel the results were good, we can focus on my hands sweep the micro-channel public number, or to my github top star, thank you

Leave a comment