This article has been authorized wechat official accountCode an eggexclusive

Preface:

A few days ago when I was watching the live broadcast on my mobile phone, I used the Chushou APP. As soon as I got to the home page, I saw the effect of the page switch inside. I think this effect can be realized with what controls. Let’s cut to the nitty-gritty. This is more interesting.

The new version of Chushou APP has been revised, please download 2.2.3.7424:

Effect:

Effect picture of Chushou APP homepage:

After seeing this effect, the first thought is that RecyclerView seems to be able to achieve this effect, but there are still many problems with RecyclerView’s own API, not to mention how to achieve it, look at the effect of the realization of it:

Picture type:

Streaming layout effect:

Multiple style effects:

Teach you how to use it in one minute:

Set the Manager:

RecyclerView chuShouView = (RecyclerView) findViewById(R.id.chushou_view);
chuShouView.setLayoutManager(new ChuShouManager());
Copy the code

Set the touch assist ChuShouCallBack class:

ItemTouchHelper.Callback callback = new ChuShouCallBack(adapter, maps, ItemTouchHelper.UP | ItemTouchHelper.DOWN);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(chuShouView);
Copy the code

Graphic setting Adapter:

chuShouView.setAdapter(adapter = new ChuShouAdapter(this, maps));
Copy the code

Flow layout setting Adapter:

chuShouView.setAdapter(adapter = new ChuShouScrollAdapter(this, items));
Copy the code

Multiple style Settings Adapter:

chuShouView.setAdapter(chuShouGridAdapter = new ChuShouGridAdapter(this, gridItems));
Copy the code

There is one thing in common between the flow layout Adapter and various types of Adapter. Their items are all with sliding structure, so I treat their structure as RecyclerView+RecyclerView. And the above image structure is RecyclerView+ImageView to process, we can focus on the ChuShouScrollAdapter and ChuShouGridAdapter code:

ChuShouGridAdapter onCreateViewHolder method:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return new MyHolder<ChuShouGridActivity.GridItem>(View.inflate(context, R.layout.scroll_item_layout, null), context) {
        @Override
        protected RecyclerView.Adapter<RecyclerView.ViewHolder> getAdapter(List<ChuShouGridActivity.GridItem> list, Context context) {
            return new ChuShouGridItemAdapter(list, context);
        }
        @Override
        protected RecyclerView.LayoutManager getLayoutManager(Context context, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
            returnnew ChuShouGridLayoutManager(context, adapter); }}; }Copy the code

ChuShouScrollAdapter onCreateViewHolder:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return new MyHolder<ChuShouScrollActivity.ShowItem>(View.inflate(context, R.layout.scroll_item_layout, null), context) {
        @Override
        protected RecyclerView.Adapter<RecyclerView.ViewHolder> getAdapter(List<ChuShouScrollActivity.ShowItem> list, Context context) {
            return new FlowAdapter(list, context);
        }
        @Override
        protected RecyclerView.LayoutManager getLayoutManager(Context context, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
            returnnew FlowLayoutManager(); }}; }Copy the code

R.l ayout. Scroll_item_layout code:

<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#cccccc"
    android:orientation="vertical">

    <com.library.chushou.view.SlideRecyclerView
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
Copy the code

Use of these attention, here is how to achieve the effect…

Explanation:

Here is how to use RecyclerView how to achieve the above effect:

Let’s start with a sketch of my ideas:

This whole thing is oneRecyclerViewAnd at the beginning, we need to define our ownLayoutmanager, visible in the code (ChuShouManager) class, theLayoutmanagerThe last item is displayed at the top of the screen, the first item is displayed on the screen, and the second to penultimate item is displayed at the bottom of the screen. So it’s always going to beRecyclerViewThe first item in, but when you swipe, you change the data source.

/** * Created by xiangcheng on 17/4/11. */ public class ChuShouManager extends RecyclerView.LayoutManager { @Override public void onLayoutChildren(RecyclerView.Recycler recycler, Recyclerview. State State) {******* omit code ****** // to prevent the number did not reach more than 1 requirementsif(getChildCount() >= 1) {// Place the first item on the screenif(i == 0) { layoutDecoratedWithMargins(childAt, 0, 0, getDecoratedMeasuredWidth(childAt), getDecoratedMeasuredHeight(childAt)); }} // Need to judge the quantityif(getChildCount() >= 2) {// From the second item to the penultimate is placed at the bottom of the screenif(i >= 1 && i < getItemCount() - 1) { layoutDecoratedWithMargins(childAt, 0, getHeight(), getDecoratedMeasuredWidth(childAt), getHeight() +getDecoratedMeasuredHeight(childAt)); }} // Quantity requirementsif(getChildCount() >= 3) {// Place the last item above the screenif (i == getItemCount() - 1) {
                    layoutDecoratedWithMargins(childAt,     
                     0, -getDecoratedMeasuredHeight(childAt), 
                     getDecoratedMeasuredWidth(childAt),0);
                }
            }
    }

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParamsRecyclerView () {// create a recyclerViewreturnnew RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); }}Copy the code

Well, the first step is finally completed, the following is the RecyclerView related code control, about the control of touch, We need access to the android. Support. V7. Widget. Helper. ItemTouchHelper. The Callback class:

From the source code screenshot to see the class is a static abstract class, indicating that we want to use, need to implement the class. Here we define an implementation class, ChuShouCallBack. The CallBack abstract class defines only our Drag action. We are actually going to use a SimpleCallback subclass, which implements our Swipe action. So we need to block the Drag action and Swipe.

Swipe action to disable Drag:

Public class ChuShouCallBack extends ItemTouchHelper. SimpleCallback {* * * * * omitted code * * * * * / / this constructor blocked swipDirection public ChuShouCallBack(RecyclerView.Adapter adapter, List mDatas) { this(adapter, mDatas, 0); } // swipDirection public ChuShouCallBack(recyclerView. Adapter Adapter, List mDatas, int swipDirection) { this(0, swipDirection); this.mAdapter = adapter; this.mDatas = mDatas; } public ChuShouCallBack(int dragDirs, int swipeDirs) { super(dragDirs, swipeDirs); } ***** omit code *****}Copy the code

Now look at the RecyclerView slide Item monitor

@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, final RecyclerView.ViewHolder viewHolder, float dX, floatdY, int act // super.onChildDraw(c,recyclerView,viewHolder,dX,dY,actionState,isCurrentlyActive); Instead of executing the parent class's method, we'll move the current item with the gesture, so we don't want log.d (TAG,"dy")
    if (height == 0) {
        height = recyclerView.getHeight();
    }
    Log.d(TAG, "dy:" + dY + ",actionState:" + actionState + ",isCurrentlyActive:" + isCurrentlyActive);
      Log.d(TAG, "lastDy:"+ lastDy); //add 2017/4/17, and then hold nextView: lastDy < 0 &&dy <= 0, lastDy < 0 &&dy >= 0 Log."height * getSwipeThreshold(viewHolder):" + height * getSwipeThreshold(viewHolder));
    if (lastDy > 0 && dY <= 0 || lastDy < 0 && dY >= 0) {
      if(lastDy > 0 && dY < = height * getSwipeThreshold (viewHolder) | | lastDy < 0 && dY > = 0) {/ / this is isCurrentlyActive = when let gofalse
        if(! isCurrentlyActive) {if(valueAnimator == null) {// From lastDy to 0 valueAnimator = valueanimator.offloat (lastDy, 0); // The maximum distance that can be pulled down or pulled up is calculated according to the critical value of SwipefloatmaxPullHeight = height * getSwipeThreshold(viewHolder); // The maximum time is 200 millisecondsfloat duration = 200 * (Math.abs(lastDy) / maxPullHeight);
                valueAnimator.setDuration((long) duration);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float animatedValue = (float) animation.getAnimatedValue();
                        float percent = Math.abs(animatedValue / height);
                        float scaleAlpha = (float) (1.0-percent * 1.0); viewHolder.itemView.setAlpha(scaleAlpha); ((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleX(scaleAlpha); ((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleY(scaleAlpha); nextView.setTranslationY(animatedValue); }}); valueAnimator.addListener(newAnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { lastDy = 0; valueAnimator = null; }}); valueAnimator.start(); }}}else{// This is the case where there is no finger replacementfloat percent = Math.abs(dY / height);
        float scaleAlpha = (float) (1.0-percent * 1.0); viewHolder.itemView.setAlpha(scaleAlpha); ((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleX(scaleAlpha); ((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleY(scaleAlpha); / / downif(dY > 0) {/ / get the item at the top of the screen nextView = recyclerView. GetChildAt (recyclerView. GetChildCount () - 1); View childAt = ((ViewGroup) nextView).getChildAt(0);if (childAt instanceof SlideRecyclerView) {
                SlideRecyclerView sl = (SlideRecyclerView) childAt;
                if (sl.getScrollY() == 0) {
                    sl.pullNextScroll();
                }
            }
            nextView.setTranslationY(dY);
            pullDown = true;
            lastDy = dY;
        } else ifNextView = recyclerView.getChildAT (1); pullDown =false; nextView.setTranslationY(dY); lastDy = dY; }}}Copy the code

It seems that the code here is so long, but in fact, in the drop-down process, the obtained nextView corresponds to the item at the top of the screen, that is, the last item of RecyclerView, because the last item is placed on the top of the screen; When pulling up, the obtained nextView corresponds to the item at the bottom of the screen, that is, the second item of RecyclerView.

The above code only handles our sliding, not our letting go. When onSwipe is triggered. Here’s a method:

/**
 * Returns the fraction that the user should move the View to be considered as swiped.
 * The fraction is calculated with respect to RecyclerView's bounds. * 

* Default value is .5f, which means, to swipe a View, user must move the View at least * half of RecyclerView'

s width or height, depending on the swipe direction. * * @param viewHolder The ViewHolder that is being dragged. * @return A float value that denotes the fraction of the View size. Default value * is .5f . */ public float getSwipeThreshold(ViewHolder viewHolder) { return .5f; } Copy the code

The source code says that as long as the sliding position exceeds the width or height of RecyclerView will trigger onSwiped method, we do not need to move the value here, the default can be, here is exactly half of the RecyclerView height distance, in the release of the trigger onSwipe method

Let’s seeonSwipedWhat has been done:

@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    Log.d(TAG, "onSwiped");
    lastDy = 0;
    refreshData(viewHolder);
    if (onSwipedListener != null) {
        onSwipedListener.onSwiped(pullDown);
    }
}
Copy the code

This handles listening for data and interface callbacks, so let’s seerefreshDataWhat the method does:

/** @param viewHolder */ private void refreshData(recyclerView.viewholder viewHolder) { / / the current item to restore viewHolder itemView. SetAlpha (1); ((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleX(1); ((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleY(1);if(pullDown) {// Restore nextView.settranslationy (-height); Rotate (mDatas, 1) rotate the collections.rotate (mDatas, 1); }elseNextview.settranslationy (height); Rotate the collections. rotate(mDatas, -1) rotate the collections. rotate(mDatas, -1); } / / refresh the item mAdapter. NotifyDataSetChanged (); }Copy the code

About the outer layerRecyclerViewSo much for sliding processing, now to introduce how to deal with the inner layer with sliding structureRecyclerView, both have sliding structures, when to let the inner layerRecyclerViewWhen to let the outer layerRecyclerViewSlide:

This time to see the inside of the SlideRecyclerView internal sliding processing:

public SlideRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle);  getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            getViewTreeObserver().removeGlobalOnLayoutListener(this);
            initView();
            if (getIsCurrentItem()) {
                addOnScrollListener(new OnScrollListener() {
                    @Override
                    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                        super.onScrollStateChanged(recyclerView, newState);
                    }
                    @Override
                    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                        super.onScrolled(recyclerView, dx, dy);
                        scrollY += dy;
                        Log.d(TAG, "scrollY:" + scrollY);
                        if(scrollY == 0) {// Make chuShouCallBack not swipe when the parent recyclerView is already at the top and is still sliding upif(dy < 0) { chuShouCallBack.setDefaultSwipeDirs(0); }}if(isSlideToBottom()) {// If the parent recyclerView is already at the bottom and is still pulling down, make chuShouCallBack not swipeif(dy > 0) { chuShouCallBack.setDefaultSwipeDirs(0); }}}}); }}}); }Copy the code

Listen to SlideRecyclerView sliding position to dynamically set the external RecyclerView whether there is a sliding process. In summary, there are two cases: one is SlideRecyclerView sliding to the top, at this time, if the slide up, need to ban the sliding of RecyclerView outer layer, Direct call chuShouCallBack. SetDefaultSwipeDirs (0) method can be banned outer slide. Another is SlideRecyclerView sliding to the bottom, at this time if to slide, also need to ban the outer layer of RecyclerView sliding.

At this point, many people wonder why listening to SlideRecyclerView slides does not open the RecyclerView code. Here open the outer RecyclerView sliding needs to be handled in onTouch. Because in SlideRecyclerView sliding monitor inside is unable to listen to if slide to the top continue to slide and slide to the bottom continue to slide up the operation, so here by ontouch coordinates of the variable to open the outer layer of RecyclerView sliding:

@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
    if (getIsCurrentItem()) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "MotionEvent.ACTION_DOWN");
                startY = e.getY();
                chuShouCallBack.setDefaultSwipeDirs(0);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "MotionEvent.ACTION_MOVE"); dataY = e.getY() - startY; // Only when sliding to the top does the item switch by judging the distance between the two pointsif (scrollY == 0) {
                    if (dataY > 0) {
                        chuShouCallBack.setDefaultSwipeDirs(ItemTouchHelper.UP | ItemTouchHelper.DOWN);
                        Log.d(TAG, "When the top is pulled down."); }}if (isSlideToBottom()) {
                    if (dataY < 0) {
                        chuShouCallBack.setDefaultSwipeDirs(ItemTouchHelper.UP | ItemTouchHelper.DOWN);
                        Log.d(TAG, "When I get to the bottom and pull it up."); }}if(scrollY ! = 0 &&! isSlideToBottom()) { chuShouCallBack.setDefaultSwipeDirs(0); Log.d(TAG,"In the middle.");
                }
                break;
            case MotionEvent.ACTION_UP:
                break; }}return super.onInterceptTouchEvent(e);
}
Copy the code

There are three cases involved:

(1)SlideRecyclerView sliding to the top, continue to slide, need to open the outer RecyclerView sliding

(2)SlideRecyclerView sliding to the bottom, continue to slide up, need to open the outer RecyclerView sliding

(3)SlideRecyclerView sliding to a middle position, whether to slide up or down the need to ban the outer layer of RecyclerView sliding

So much for the core code, if you have any questions about the use, you can directly see the demo, or directly discuss with me, welcome to issue

Conclusion:

  • Structural analysis: The overall outer layer is a large RecyclerView(defined as ScrollRecyclerView, in order to provide its own ChuShouCallBack externally). The items inside are divided into two situations, one is with sliding structure and the other is non-sliding structure. Sliding structure defines a RecyclerView(here defined as SlideRecyclerView, to handle sliding).

  • Analyzing the arrangement of items: This is where the ChuShouManager is responsible for placing the last item at the top of the screen, the first item in the middle of the screen, and the second to penultimate item at the bottom of the screen.

  • Handling touch actions: ChuShouCallBack plays this role, handling pull-ups and pull-downs that change the transparency and shift of an item. Finally, restore the item state and change the data source at onSwipe

  • Process item itself with sliding (SlideRecyclerView) and the outer RecyclerView sliding conflict: here is the analysis of when to ban the outer RecyclerView sliding, when to open the sliding. The principle is when item sliding to the top, if continue to slide up to ban the outer RecyclerView sliding, if continue to slide to open the outer RecyclerView sliding; When item slides to a certain position in the middle, no matter it continues to slide up or down, it is forbidden to slip the outer RecyclerView; When item sliding to the bottom, if continue to slide up to open the outer RecyclerView sliding, if continue to slide to ban the outer RecyclerView sliding.

Subsequent additions:

Sliding controls also have listViews, ScrollViews, and so on

Welcome guests to our shop :184793647(QQ group)

Gradle depends on:

allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}

dependencies {
       ...
       compile 'com. Making. 1002326270 xc: ChuShouView - master: v1.3'. }Copy the code

About me:

email: [email protected]

CSDN: blog.csdn.net/u010429219/…

Making: github.com/1002326270x…

More articles you like

Imitation 360 mobile phone assistant download button
The effect of the ofo app’s homepage menu
Design a bank APP maximum amount control
Allows you to implement a ViewGroup with a defined number of rows and an item centered streaming layout
Customize a view that looks like an address selector
3D version of page turning announcement effect
One minute to complete the cool sliding switching effect of the Chushou APP home page
Fast use of RecyclerView LayoutManager to build flow layout
Bessel curve with their own written a power display control
Create a custom calendar quickly