Xu Gong, programmer of my public account, has four years of working experience in Dachang. He replies to the dark knight and gets a copy of Android learning video. He replies to Xu Gong 666, where he can get my carefully organized resume template and lead you to Dachang.
You can use CoordinatorLayout to create all kinds of cool things
Custom Behavior – Mimicking Zhihu, FloatActionButton hidden and displayed
NestedScrolling mechanism for in-depth parsing
You can read the source code for CoordinatorLayout step by step
Custom Behavior – implementation of the discovery page imitating Sina Weibo
ViewPager, ScrollView nested ViewPager sliding conflict resolved
Custom behavior – perfect imitation QQ browser home page, Meituan business details page
preface
I remember that two years ago, I wrote an article about custom behavior. The realization of custom behavior – imitating the discovery page of Sina Weibo has been read more than 10,000 times until now.
Today, the behavior is updated to include the following features over the behavior of two years ago
- Add listening callback in cascade sliding process, convenient external according to the sliding distance, the corresponding animation, show cool UI, through setPagerStateListener set callback listening
- While sliding to the top, you can set whether you can slide the Head down using setCouldScroollOpen
- Add the Fling callback to slide the Content list using setOnHeaderFlingListener
- HeaderBehavior, ContentBehavior code optimization, and business logic separation, easy reuse.
Directions for use
rendering
Let’s first take a look at the effect of sina Weibo discovery page:
Let’s take a look at what we did two years ago with Sina Weibo
Imitation QQ browser
Imitation of Meituan merchant details page:
Analysis:
There are two states, open and close.
- The open state is when Tab+ViewPager has not been swiped to the top and the header has not been completely removed from the screen
- The close state is when Tab+ViewPager slides to the top and the Header is removed from the screen
From the renderings, we can see that in the open state, when we slide up the RecyclerView inside the ViewPager, RecyclerView will not move up (the sliding event of RecyclerView is handed to the external container for processing, The entire layout (Header + Tab +ViewPager) is offset upwards. When Tab slides to the top, the RecyclerView inside the ViewPager slides up, and the RecyclerView slides up normally, that is, the external container does not intercept the sliding event.
In open state, we set the SwipeRefreshLayout setEnabled to false so that events will not be blocked. When the page is closed, set the SwipeRefreshLayout setEnabled to TRUE to support the pull-down refresh.
Based on the above analysis, we can divide the whole effect into three parts here
The first part of the Header: When the Header has not been slid to the top (i.e., when open), follow the finger to slide the second part of the Content: When we slide up, when the Header is open, the Header slides up, the recyclerView of the Content part will not slide, when the Header is close, the content part slides up, RecyclerView slides up. When we slide down, the header will not slide, it will only slide the Content part of recyclerView 3 search: When we slide up, the search part will slide, and eventually stay in a fixed position.
We define this three-part relationship to mean that Content depends on headers. When the Header moves, the Content moves with it. Therefore, when dealing with sliding events, we only need to deal with the Behavior of the Header part, and the Behavior of the Content part does not need to deal with sliding events. We only need to rely on the Header and move accordingly. The behavior in the Search part also doesn’t need to handle the sliding event. It just relies on the Header and moves accordingly.
As for the specific implementation, you can see the implementation of custom behavior-imitating Sina Weibo discovery page. The core idea is similar, and it will not be repeated here.
Directions for use
Here we have simulated QQ browser demo to illustrate:
Let’s take a look at how to use it: In a nutshell, there are only two steps:
- The first step is to specify our corresponding behaviors in the header and content sections respectively in the XML file
- In the second part, set some configuration parameters in the code
Step 1: Write the XML file and specify the appropriate behavior
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_light"
android:fitsSystemWindows="true">
<! -- Header -->
<FrameLayout
android:id="@+id/id_uc_news_header_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/behavior_qq_browser_header_pager">
<com.xj.qqbroswer.behavior.base.NestedLinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/header_height"
android:orientation="vertical">
<TextView
android:id="@+id/news_tv_header_pager"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:gravity="center"
android:text="QQBrowser Header"
android:textColor="@android:color/white" />
</com.xj.qqbroswer.behavior.base.NestedLinearLayout>
</FrameLayout>
<! ContentProvide -->
<LinearLayout
android:id="@+id/behavior_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_behavior="@string/behavior_contents">
<android.support.design.widget.TabLayout
android:id="@+id/id_uc_news_tab"
android:layout_width="match_parent"
android:layout_height="@dimen/tabs_height"
android:background="@color/colorPrimary"
app:tabGravity="fill"
app:tabIndicatorColor="@color/colorPrimaryLight"
app:tabSelectedTextColor="@color/colorPrimaryLight"
app:tabTextColor="@color/colorPrimaryIcons" />
<android.support.v4.view.ViewPager
android:id="@+id/id_uc_news_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F0F4C3">
</android.support.v4.view.ViewPager>
</LinearLayout>
<! Part - search - >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/header_title_height"
app:layout_behavior="@string/behavior_search">
<android.support.v7.widget.SearchView
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_marginRight="50dp"
android:background="@android:color/white"
app:defaultQueryHint="Search"
app:queryHint="Search">
</android.support.v7.widget.SearchView>
<android.support.v7.widget.AppCompatImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:src="@mipmap/camera"
android:tint="@android:color/white" />
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>
Copy the code
Step 2: Dynamically set some parameters in the code
private void initBehavior(a) {
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(a) {
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(a) {
Snackbar.make(mNewsPager, "pager opened", Snackbar.LENGTH_SHORT).show();
setFragmentRefreshEnabled(false); }});// Set it to the negative of header height
mHeaderBehavior.setHeaderOffsetRange(-resources.getDimensionPixelOffset(R.dimen.header_height));
// Set header close to open by sliding
mHeaderBehavior.setCouldScroollOpen(false);
mContentBehavior = (QQBrowserContentBehavior) ((CoordinatorLayout.LayoutParams) findViewById(R.id.behavior_content).getLayoutParams()).getBehavior();
// Set the id to depend on. In this case, set it to Header Layout ID
mContentBehavior.setDependsLayoutId(R.id.id_uc_news_header_pager);
// Sets the final resting position of the Content section
mContentBehavior.setFinalY(resources.getDimensionPixelOffset(R.dimen.header_title_height));
}
Copy the code
MHeaderBehavior setHeaderOffsetRange set the Header part of the offset, we are implemented through translationY, so we usually opposite can be set to the Header height. MHeaderBehavior. SetCouldScroollOpen (false), set the header when the close whether can open by sliding.
mContentBehavior.setDependsLayoutId(R.id.id_uc_news_header_pager); Set which ID to rely on, in this case the Header Layout ID. MContentBehavior. SetFinalY sets the position of the content part eventually stop.
Let’s look at the 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();
}
Copy the code
There are three main methods. The first method, onPagerClosed, will call back when the header is close, and the second method, onScrollChange, will call back when the header slide distance changes. It has three parameters: isUp for sliding up, dy for header offset, and type for touch or non-touch.
If you want to do something really cool, you can animate each View in the onScrollChange method, depending on how far it slides.
Copy meitu business details page
The steps are the same as the steps of the imitation QQ browser above, and the same steps are not repeated here. Several key points are said: first: The page header when close, we can through the slide open the header, this is by calling mHeaderBehavior. SetCouldScroollOpen (true); The implementation. The second: When you slide the header to fling, you can see that the recyclerView of the Content part also slides. This is done by the Fling event of the header. On the onFlingStart manually call RecyclerView smoothScrollBy to slide.
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(){}});Copy the code
Run into the pit of
The header section cannot respond to the slide event
We customize a NestedLinearLayout, rewrite its onTouchEvent, and send events to NestedScrollingParent via the NestedScrolling mechanism. That’s CoordinatorLayout, and the NestedScrollingParent gives it to the behavior of the child View.
@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 slide up, dy>0 pull down
if (dy < 0) { // The parent class handles the slide
if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) // If a parent class that supports nested scrolling is found
&& dispatchNestedPreScroll(0, -dy, consumed, offset)) {//
// The parent class does some scrolling}}else {
if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) // If a parent class that supports nested scrolling is found
&& dispatchNestedScroll(0.0.0, -dy, offset)) {//
// The parent class does some scrolling}}break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
stopNestedScroll();
break;
}
return super.onTouchEvent(event);
}
Copy the code
When we set the click event to the header’s child View, we can’t slide the header
If you’re familiar with the Android event distribution mechanism, you know that the default event delivery mechanism in Android is like this,
When a TouchEvent occurs, the Activity first passes the TouchEvent to the topmost View, and the TouchEvent first reaches the dispatchTouchEvent of the topmost View. It is then distributed by the dispatchTouchEvent method.
-
If dispatchTouchEvent returns true consumption event, the event is terminated.
-
If dispatchTouchEvent returns false, the onTouchEvent handler is passed back to the parent View;
The onTouchEvent event returns true, the event terminates, returns false, and is handled by the parent View’s onTouchEvent method
-
If dispatchTouchEvent returns super, it calls its own onInterceptTouchEvent method by default
By default, the interceptTouchEvent calls the super method, which by default returns false, If the interceptTouchEvent returns true, it is intercepted, If the interceptTouchEvent returns false, it is passed to the child view, whose dispatchTouchEvent starts the event distribution.
So, when we set a click event for the child View, the default parent does not block the event and goes to the child View’s onToucheEvent event, which is consumed because of the click event. So the ACTION_MOVE event in the parent View onTouchEvent is not called back.
Solutions: Rewrite the onInterceptToucheEvent of NestedLinearLayout to return true when it is an ACTION_MOVE event, which will call its own onTouchEvent event to ensure that it can slide.
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownY = (int) event.getRawY();
// Tell the parent view when it starts sliding
startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL
| ViewCompat.SCROLL_AXIS_VERTICAL);
break;
case MotionEvent.ACTION_MOVE:
// Make sure that ACTION_DOWN events are not consumed
if (Math.abs(event.getRawY() - mDownY) > mScaledTouchSlop) {
logD("onInterceptTouchEvent: ACTION_MOVE mScaledTouchSlop =" + mScaledTouchSlop);
return true; }}return super.onInterceptTouchEvent(event);
}
Copy the code
A click event triggers ACTION_DOWN, ACTION_MOVE, and ACTION_UP. If we return true in ACTION_MOVE, it will invalidate the onClick event of the child View.
Solutions:
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mScaledTouchSlop = configuration.getScaledTouchSlop();
if (Math.abs(event.getRawY() - mDownY) > mScaledTouchSlop) {
return true; }Copy the code
For slide resolution, see my previous blog :ViewPager, ScrollView nested ViewPager slide resolution
How do I determine whether the header is a Fling action
We do this with the gesture processor GestureDetector, but you can also do it with VelocityTracker, which is just a little bit more cumbersome
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
}
GestureDetector.OnGestureListener onGestureListener = new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false; } -- -- -- -- --// Omit some code
@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);
Copy the code
digression
Sometimes, it’s really important to take notes.
This time I write this blog, because I want to do a similar effect in the project. At first, I didn’t have any ideas. But I remember clearly that I wrote a similar article two years ago, and the specific implementation principle has long been forgotten. I looked at blogs from two years ago, did some brainstorming, moved the code into the project, and found a few bugs. Tinker around and fill in the holes.
Think about it, if the principle had not been written down, this effect is really difficult to achieve. If you’re not familiar with Coordinatorlayout, behavior,NestedScroll, you can’t do it. Two years ago, WHEN I wrote this blog, I received a lot of private letters. Some feedback said that they could not achieve this effect after more than two weeks. Thank you very much for writing this blog. So try taking more notes from now on. Really, a good memory is better than a bad pen.
The second thing that struck me was, at first, I looked at the code THAT I had written two years earlier, and my initial reaction was, oh, shit. Indeed, many places are poorly written. Behavior coupled business logic is difficult to reuse and maintain. So, this time, I’m going to pull behavior out in my free time, and then I’m going to achieve a similar effect in the future, easily. Biu biu biu.
With all that said, here’s the summary
- Take notes when you don’t, especially when it comes to principles
- Be in awe of the code, don’t say much, and learn it yourself
- Keep a humble heart
CoordinatorLayoutExample
If you think the effect is good, you can scan and follow my wechat official account, or go to star on my Github, thank you
Recommended reading
How should a programmer write his resume, a 5 years big factory elder brother talk to you
My 5 years of Android learning road, those years together stepped on the pit
- Public number programmer Xu Gong reply dark horse, get Android learning video
- Public number programmer Xu Gong replied xu Gong 666, get resume template, teach you how to optimize your resume, into the factory
- Public number programmer Xu Gong replied to the interview, you can get the interview common algorithm, sword refers to the offer question solution
- Public number programmer Xu Gong replies horse soldier, can obtain horse soldier study video