Similar to Jingdong, Taobao and other secondary hover. Reference + practice


First, the convention of the effect map

Second, making

The code address, welcome to correct https://github.com/MNXP/SlideTop

Controls used in XML layout

1, PullRefreshLayout, to borrow the great god of https://github.com/genius158/PullRefreshLayout) 2, 3, AppBarLayout CoordinatorLayoutCopy the code

Four, implementation,

1, the implementation of the layout

A few points to note: AppBarLayout =".weight.MyBehavior" AppBarLayout =".weight.MyBehavior" The layout of the slide is the need to disappear, set the app: layout_scrollFlags = "scroll | exitUntilCollapsed" scroll the scroll, ExitUntilCollapsed can have shadow effect at the top and RecyclerView App :layout_behavior="@string/appbar_scrolling_view_behavior"Copy the code
<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout 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:orientation="vertical"> <RelativeLayout android:layout_width="match_parent"  android:layout_height="60dp" android:background="#ffffff" android:orientation="vertical"> <TextView Android :layout_width="match_parent" Android :layout_height="match_parent" Android :gravity="center" Android :text=" top slide" android:textColor="@color/black" android:textSize="20sp" /> <TextView android:layout_width="match_parent" android:layout_height="1dp" android:layout_alignParentBottom="true" android:background="#dddddd"/> </RelativeLayout> <com.xp.slide.weight.refreshlayout.PullRefreshLayout android:id="@+id/swipe_refresh_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:prl_pullDownMaxDistance="300dp" app:prl_twinkEnable="true"> <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/home_top_view" android:orientation="vertical"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/app_bar_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ffffff" app:layout_behavior=".weight.MyBehavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:layout_width="match_parent" android:layout_height="150dp" android:src="@mipmap/home_c"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/top_img_rv" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> <RelativeLayout android:id="@+id/home_tab_container_layout" android:layout_width="match_parent" android:layout_height="55dp" android:gravity="center" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" Android :layout_marginLeft="20dp" Android :textSize="15sp" Android :textColor="#222222" Android :text=" Hover title "/> <ImageView android:id="@+id/filter_layout" android:layout_width="40dp" android:layout_height="40dp" android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:layout_marginEnd="20dp" android:scaleType="fitXY" android:src="@mipmap/home_icon" /> </RelativeLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/bottom_img_rv" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> </com.xp.slide.weight.refreshlayout.PullRefreshLayout> </LinearLayout>Copy the code

2. First solve the sliding conflict between the PullRefreshLayout and CoordinatorLayout (which depends on AppBarLayout)

Through AppBarLayout listening addOnOffsetChangedListener CoordinatorLayout whether sliding to the top, whether can set PullRefreshLayout pull-up refreshCopy the code
/ / record AppBar rolling distance appBarLayout. AddOnOffsetChangedListener (View: : setTag); homeRefreshLayout.setOnTargetScrollCheckListener(new PullRefreshLayout.OnTargetScrollCheckListener() { @Override public Int appbarOffset = ((AppBarLayout.getTag ()) int appbarOffset = ((AppBarLayout.getTag ())) instanceof Integer)) ? (int) appBarLayout.getTag() : 0; return appbarOffset ! = 0; } @Override public boolean onScrollDownAbleCheck() { return true; }});Copy the code

3, Enable the AppBarLayout slide (do not set it, but sometimes it will slide problems)

Note 📢 : this should be set after the data is loaded, otherwise it will not workCopy the code
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams(); MyBehavior behavior = (MyBehavior) params.getBehavior(); try { if (behavior! =null){ behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() { @Override public boolean canDrag(@NonNull AppBarLayout appBarLayout) { isFirstData = true; // To enable scrolling for the collapsetoolbar return true; }}); } } catch (Exception e) { }Copy the code

4, think very perfect ┭┮﹏┭┮, but run into a small problem

The problem? After sliding the top, slide the recyclerView below, so that recyclerView is not the first item to display, release the hand, and then slide down the "hover title", find that you can slide down, 🤩, is the taste of bug. The following figureCopy the code

So let’s do that
The solution is to set whether AppBarLayout can slide according to the following RecyclerView sliding, Set the behavior. SetCanMove (Position <1); (3) Whether the implementation of MyBehavior can slide on the codeCopy the code
/ / the first complete item (1) set up to monitor RecyclerView bottomRv. AddOnScrollListener (new RecyclerView. OnScrollListener () {@ Override public void  onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == SCROLL_STATE_IDLE) { if (bottomRv ! = null && bottomRv.getLayoutManager() instanceof LinearLayoutManager) { LinearLayoutManager layoutManager = (LinearLayoutManager) bottomRv.getLayoutManager(); if (layoutManager ! = null) {/ / slide according to item set can top sliding int firstCompletelyVisible = layoutManager. FindFirstCompletelyVisibleItemPosition (); initAppbar(firstCompletelyVisible); }}}}}); // Set behavior. SetCanMove (Position <1); private boolean isFirstData; private int oldPosition = -2; public void initAppbar(int position) { if (oldPosition == position){ return; } oldPosition = position; CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams(); MyBehavior behavior = (MyBehavior) params.getBehavior(); try { if (behavior! =null){ if (position == -1){ behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() { @Override public boolean canDrag(@NonNull AppBarLayout appBarLayout) { isFirstData = true; // To enable scrolling for the collapsetoolbar return true; }}); }else {// If recyclerView is not the first item, disable the toolbar slide behavior. SetCanMove (position<1); }}} catch (Exception e) {}} public Boolean onInterceptTouchEvent(@nonnull) CoordinatorLayout parent, @NonNull AppBarLayout child, @NonNull MotionEvent ev) { if (! canMove && ev.getAction() == MotionEvent.ACTION_DOWN){ return false; } return super.onInterceptTouchEvent(parent, child, ev); } public void setCanMove(boolean canMove){ this.canMove = canMove; }Copy the code

5. Complete code

The Activity codeCopy the code
public class MainActivity extends AppCompatActivity { private RecyclerView topRv; private RecyclerView bottomRv; private AppBarLayout appBarLayout; private PullRefreshLayout homeRefreshLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); appBarLayout = findViewById(R.id.app_bar_layout); homeRefreshLayout = findViewById(R.id.swipe_refresh_layout); topRv = findViewById(R.id.top_img_rv); bottomRv = findViewById(R.id.bottom_img_rv); topRv.setLayoutManager(new LinearLayoutManager(this)); bottomRv.setLayoutManager(new LinearLayoutManager(this)); initView(); initData(); } private void initView() { StoreHouseHeader header = new StoreHouseHeader(this); header.setPadding(0, 20, 0, 20); header.initWithString("XIANGPAN"); header.setTextColor(0xFF222222); homeRefreshLayout.setHeaderView(header); homeRefreshLayout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListenerAdapter() { @Override public void onRefresh() { initData(); CheckHandler. SendEmptyMessageDelayed (0200); }}); / / record AppBar rolling distance appBarLayout. AddOnOffsetChangedListener (View: : setTag); homeRefreshLayout.setOnTargetScrollCheckListener(new PullRefreshLayout.OnTargetScrollCheckListener() { @Override public Int appbarOffset = ((AppBarLayout.getTag ()) int appbarOffset = ((AppBarLayout.getTag ())) instanceof Integer)) ? (int) appBarLayout.getTag() : 0; return appbarOffset ! = 0; } @Override public boolean onScrollDownAbleCheck() { return true; }}); } private void initData() { initTop(); initBottom(); if (! isFirstData) { initAppbar(-1); } appBarLayout.setExpanded(true, false); } private void initBottom() { PhotoAdapter bottomAdapter = new PhotoAdapter(); bottomRv.setAdapter(bottomAdapter); bottomAdapter.setDataList(10); bottomRv.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == SCROLL_STATE_IDLE) { if (bottomRv ! = null && bottomRv.getLayoutManager() instanceof LinearLayoutManager) { LinearLayoutManager layoutManager = (LinearLayoutManager) bottomRv.getLayoutManager(); if (layoutManager ! = null) {/ / slide according to item set can top sliding int firstCompletelyVisible = layoutManager. FindFirstCompletelyVisibleItemPosition (); initAppbar(firstCompletelyVisible); }}}}}); } private void initTop() { PhotoAdapter topAdapter = new PhotoAdapter(); topRv.setAdapter(topAdapter); topAdapter.setDataList(4); } private boolean isFirstData; private int oldPosition = -2; public void initAppbar(int position) { if (oldPosition == position){ return; } oldPosition = position; CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams(); MyBehavior behavior = (MyBehavior) params.getBehavior(); try { if (behavior! =null){ if (position == -1){ behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() { @Override public boolean canDrag(@NonNull AppBarLayout appBarLayout) { isFirstData = true; // To enable scrolling for the collapsetoolbar return true; }}); }else {// If recyclerView is not the first item, disable the toolbar slide behavior. SetCanMove (position<1); } } } catch (Exception e) { } } public Handler checkHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); If (homeRefreshLayout! = null) { homeRefreshLayout.refreshComplete(); }}}; }Copy the code
MyBehavior codeCopy the code
public class MyBehavior extends AppBarLayout.Behavior {


    private boolean canMove = true;

    public MyBehavior() {

    }

    public MyBehavior(Context context, AttributeSet attrs) {

        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(@NonNull CoordinatorLayout parent, @NonNull AppBarLayout child, @NonNull MotionEvent ev) {
        if (!canMove && ev.getAction() == MotionEvent.ACTION_DOWN){
            return false;
        }
        return super.onInterceptTouchEvent(parent, child, ev);
    }

    public void setCanMove(boolean canMove){
        this.canMove = canMove;
    }

    public boolean isCanMove() {
        return canMove;
    }
}

Copy the code

That’s all. I’ll update it later. If you have any suggestions or comments, please contact us in time.