Put an effect map first.
Demo is based on MVVM mode. We welcome your criticism and correction.
Among them, Banner's infinite rotation uses PageSnapHelper, and subsequent RecycleView can also achieve more effects similar to ViewPage.
Project Links: https://github.com/ly85206559/demo4Fish
It would be best if it could help you.
You can see that the page can be roughly divided into these parts.
1. The top one is a rotated Banner.
2. There may be a list of other functions in the middle.
3. Finally, the Tab page (here are two new and nearby lists)
OK, there may be two ways to look at such layout requirements
-
The whole layout is a RefreshLayout layout with embedded RecycleView, while Banner pages, other feature lists and TabLayout are added to RecycleView as the header of RecycleView. Below TabLayout are the real list items.
-
The overall layout is also a RefreshLayout layout. Inside is a NestScrollView, Banner page, list of other functions. TabLayout is arranged in NestScrollView in turn, and then the bottom layout is a FrameLayout, which switches different Fragment s when TabLayout switches.
Demo uses the first approach, and the second approach takes into account the conflict between Swipe RefreshLayout and the sliding of the internal FrameLayout, and then tries to write it later.
Next, consider the issues that need to be considered.
- TabLayout needs to be fixed to the top
- The first time you load data, you need a Loading prompt. In Demo, there is a blank waiting page for a small fish.
- Because using a data set, when switching back and forth in TabLayout, you need to ensure that the data set is in the right place (for example, the fresh list is currently in Position 1, switching to the nearby list I slipped to Position 2, and when I cut back fresh, I need to go back to Position 1).
Here are some core codes and ideas
First, the layout is simple. Swipe RefreshLayout includes a FrameLayout, and then a RecycleView in the FrameLayout
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/layout_refresh" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> </android.support.v4.widget.SwipeRefreshLayout>
Next, look at how StickyHead is implemented
//Normal TabLayout layout private TabLayout mTabLayout; //Viscous TabLayout layout (for fixing on top) private TabLayout mStickyTabLayout; //Y coordinates of sticky layout (user judges whether sticky layout is displayed) private int mStickyPositionY; //Main List Layout private RecyclerView mHomeList;
This is the definition of variables. The following class is for me to extract variables from some page logic.
public class HomeEntity extends BaseObservable { //List Type 0: Fresh 1: Nearby public static final int LIST_TYPE_FRESH = 0; public static final int LIST_TYPE_NEAR = 1; private int bannerCount; private int listType = LIST_TYPE_FRESH; //Fresh and nearby initial loading state private boolean refreshLoading; private boolean nearLoading; //Is the Home Page being Refreshed Drop-Down private boolean refreshing; //Get more View status values fresh and nearby (Loading More status when user records TabLayout switching) private int refreshMoreStatus; private int nearMoreStatus; //More Active Status on Home Page private int loadingMoreStatus; }
This is the definition of the variable, and then initializes two TabLayouts, mainly because you need to listen for TabLayout switching.
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { int position = tab.getPosition(); //Set Tab to Paste Tab Layout Check Tab if (!mStickyTabLayout.getTabAt(position).isSelected()) { mStickyTabLayout.getTabAt(position).select(); mViewModel.changeHomeData(position); } } @Override public void onTabUnselected(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) { } }); mStickyTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { int position = tab.getPosition(); if (!mTabLayout.getTabAt(position).isSelected()) { mTabLayout.getTabAt(position).select(); mHomeList.stopScroll(); //mAdapter.setEnableLoadMore(false); mViewModel.changeHomeData(position); ...... } } @Override public void onTabUnselected(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) { } });
This section of logic is relatively simple, that is to achieve the unification of keeping TabLayout switching state. When TabLayout switching, you need to set the Tab selected by Sticky TabLayout. The sentence "mViewModel. ChangeeHomeData" (position) is for switching data, which will be analyzed below.
Next comes the important StickyHead code
mHomeList.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { int[] location = new int[2]; mTabLayout.getLocationInWindow(location); int count = mViewContainer.getChildCount(); if (location[1] <= mStickyPositionY) { if (count == 1) { mViewContainer.addView(mStickyTabLayout); mBinding.layoutRefresh.setEnabled(false); } } else { if (count > 1) { mViewContainer.removeView(mStickyTabLayout); //mOffsetY = DisplayUtil.dip2px(mContainer.getContext(), 46); //mRefreshPosition = mAdapter.getHeaderLayoutCount(); //mNearPosition = mAdapter.getHeaderLayoutCount(); mBinding.layoutRefresh.setEnabled(true); } } //if (mInitPositionY == -1) { //mInitPositionY = location[1]; //} //mHomeListPositionY = location[1]; } });
The main logic is to get the position of TabLayout in the window first. If the Y coordinate is less than the Y coordinate of the paste head, the paste head will be added to the layout and displayed. Otherwise, the paste head layout will be removed from the layout. The count value is determined to prevent duplicate addition and duplicate removal of the paste head layout. mBinding.layoutRefresh.setEnabled(true/false) is designed to eliminate the drop-down refresh error of the outer Swipe Refresh Layout when the head is attached to the top. The commented-out code will be discussed below.
Just one StickyHead is needed to implement all of the above code. A minor problem encountered during testing is an error in the RecycleView's return to its original position caused by focus reset. Here's a temporary solution.
LinearLayoutManager manager = new LinearLayoutManager(mContainer.getContext()) { @Override public boolean onRequestChildFocus(RecyclerView parent, RecyclerView.State state, View child, View focused) { //TODO Temporarily Handles View Focus return true; } };
Here's how to implement a waiting page when first loading fresh or nearby data
The main idea is this.
- The waiting Loading View is added as the head of RecycleView after TabLayout, and the Loading View is set to be invisible when the data is loaded.
- Because TabLayout will switch, the data of RecycleView will be redrawn, which will cause RecyView to return to its original location, so you need to record the location of RecycleView and then slide manually to the location of the record.
Specifically, let's look at the code.
private int mHomeListPositionY;//Used to identify the current RecycleView location private int mInitPositionY = -1;//Y-coordinate of RecycleView in initial state //Here is the sliding listener for RecycleView, which records the location of RecycleView. Here is actually the location of mTabLayout. mHomeList.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { int[] location = new int[2]; mTabLayout.getLocationInWindow(location); int count = mViewContainer.getChildCount(); if (mInitPositionY == -1) { mInitPositionY = location[1]; } mHomeListPositionY = location[1]; } }); //This function is used to manually slide RecycleView to the correct position. private void setLoadingView(boolean visible, int type) { int position; if (type == HomeEntity.LIST_TYPE_FRESH) { position = mRefreshPosition; } else { position = mNearPosition; } if (visible) { mLoadingView.setVisibility(View.VISIBLE); if (mStickyTabLayout.getVisibility() == View.VISIBLE) { LinearLayoutManager layoutManager = (LinearLayoutManager) mHomeList.getLayoutManager(); layoutManager.scrollToPositionWithOffset(0, mHomeListPositionY - mInitPositionY); } } else { mLoadingView.setVisibility(View.GONE); LinearLayoutManager layoutManager = (LinearLayoutManager) mHomeList.getLayoutManager(); if (mViewContainer.getChildCount() > 1) { layoutManager.scrollToPositionWithOffset(position, mStickyTabLayout.getHeight()); } else { layoutManager.scrollToPositionWithOffset(0, mHomeListPositionY - mInitPositionY); } } }
Finally, let's look at the implementation of pages when fresh and nearby loads are more.
Here the Adapter uses third-party BRVAH, so relative to LoadingMore's status BRVAH, I sealed it, because although it is a List, but actually two lists reuse a List, so here we need to record two LoadingMore status, when the list is convenient to switch, the LoadingMore status is correct, below. Look at the main code
if (propertyId == BR.refreshLoading) { if (HomeEntity.LIST_TYPE_FRESH != entity.getListType()) { return; } if (mLoadingView.getVisibility() == View.GONE) { mAdapter.setEnableLoadMore(true); } } else if (propertyId == BR.nearLoading) { if (HomeEntity.LIST_TYPE_NEAR != entity.getListType()) { return; } if (mLoadingView.getVisibility() == View.GONE) { mAdapter.setEnableLoadMore(true); } } else if (propertyId == BR.loadingMoreStatus) { int status = entity.getLoadingMoreStatus(); mAdapter.setEnableLoadMore(true); if (LoadMoreView.STATUS_DEFAULT == status) { mAdapter.loadMoreComplete(); } else if (LoadMoreView.STATUS_END == status) { mAdapter.loadMoreEnd(); } else if (LoadMoreView.STATUS_FAIL == status) { mAdapter.loadMoreFail(); } }
BR.refreshLoading and BR.nearLoading are both listening for the first load, here
if (mLoadingView.getVisibility() == View.GONE) {
mAdapter.setEnableLoadMore(true);
}
This is to prevent the Loading More layout from being displayed again when the Loading page is first loaded.
BR.loadingMoreStatus This is the Adapter that monitors the status of LoadingMore to update the List
The other main ViewModel code is in the HomeViewModel. Major points
- Logic of Paste Header Layout
- TabLayout handover results in changes in data sets and locations
- TabLayout switching needs to be considered when loading more