浙江铃声推荐联盟

百行代码打造高级联动特效

猿份2019-04-14 11:44:52



前两天突然看到一个联动效果蛮不错的,虽然不知道具体什么地方会用到。不过也随手鲁了一个。效果如下图: 

效果是不是挺好玩的~~。那么让我们接下来一步步的分析一下。

思路

首先,让我们想象一下如何实现?自定义view?自定义layout?还是什么?首先这是多层布局的嵌套。肯定会发生的就是事件拦截和分发。我们想像一下Coordinatorlayout+Appbarlayout+CollapsingToolbarLayout和我们现在的效果是不是差不多?只是上者是一个这个是多个而已。于是我的想法就是鲁一个behavior来实现此效果。

自定义layout

首先我们把需要的title和Recyclerview放入一个自定义布局里面。这是为了简单我们的代码。所以把功能放到自定义layout里面。代码如下:

 private void initial(Context context, AttributeSet attrs) {

        LayoutInflater.from(context).inflate(R.layout.cardlayout, this);

        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SWCardLayout);

        TextView title = (TextView) findViewById(R.id.title);

        title.setText(array.getText(R.styleable.SWCardLayout_text));

        title.setBackgroundColor(array.getColor(R.styleable.SWCardLayout_backgroundcolor,Color.BLACK));

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler);

        recyclerView.setLayoutManager(new LinearLayoutManager(context));

        NumberAdapter numberAdapter = new NumberAdapter(context);

        recyclerView.setAdapter(numberAdapter);

        array.recycle();

    }


    @Override

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        if (w != oldw || h != oldh) {

            titleheight = findViewById(R.id.title).getMeasuredHeight();

        }

    }


    public int getTitleheight() {

        return titleheight;

    }


这边代码我就不多说了。返回title的高度为了后面的计算。

随手鲁一个behavior

我们先自定义一个behavior放入我们刚刚写的自定义layout。代码如下:


public class CardBehavior extends Behavior<SWCardLayout> {


    private int childheight = 0;


    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, SWCardLayout child,

                                       View directTargetChild, View target, int nestedScrollAxes) {

        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 && directTargetChild == child;

    }

}


至于nestedscrolling的这个方法我就不多少了。前面有几篇讲到这个的。 
现在我们需要思考的是如何摆放他们的位置。其实behavior都帮我们处理好了。那就是onLayoutChild和OnmesureChild。控制他们的宽高和布局显示都在这2个方法里面。接下来我们看下这块的代码如何去写:


    //控制子控件的onlayout和onmesure

    @Override

    public boolean onLayoutChild(CoordinatorLayout parent, SWCardLayout child, int layoutDirection) {

        //按照默认的情况控制,会导致child重叠

        parent.onLayoutChild(child, layoutDirection);

        //控制顶部的偏移量(上一个距离顶部的边距+自身title高度)

        SWCardLayout frontChild = getFrontChild(parent, child);

        if (frontChild != null) {

            int offset = frontChild.getTop() + frontChild.getTitleheight();

            child.offsetTopAndBottom(offset);

        }

        childheight = child.getTop();

        return true;

    }

       @Override

    public boolean onMeasureChild(CoordinatorLayout parent, SWCardLayout child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {

        //防止布局的重绘

        int offset = getChildOffset(parent, child);

        int height = View.MeasureSpec.getSize(parentHeightMeasureSpec) - offset;

        child.measure(parentWidthMeasureSpec, View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));

        return true;

    }


我们来分析下,先从onmesure说起。我们需要得到他的偏移量,也就是上一个的title的高度。如果是若干个布局,那就是若干的title的高度。计算title的偏移量的代码很简单,就是叠加title的高度:



 private int getChildOffset(CoordinatorLayout parent, SWCardLayout child) {

        int offset = 0;

        for (int i = 0; i < parent.getChildCount(); i++) {

            View view = parent.getChildAt(i);

            if (view != child) {

                if (view instanceof SWCardLayout) {

                    offset += ((SWCardLayout) view).getTitleheight();

                }

            }

        }

        return offset;

    }



得到它的偏移量之后我们直接通过子view的onmesure布局扔进去。所以这个不是很难。

现在我们继续看onlayoutchild方法。我注释也写的比较明确了。如果单独通过layout放置的话,child会重叠在一起。那么计算方法就是计算前一个child顶部的距离加上他自身的title的高度。那么我们来看看如何得到前一个child。其他代码很简单,遍历父view就可以。代码如下:

 //得到前一个child

    private SWCardLayout getFrontChild(CoordinatorLayout parent, SWCardLayout child) {

        int index = parent.indexOfChild(child);

        for (int i = index - 1; i >= 0; i--) {

            View view = parent.getChildAt(i);

            if (view instanceof SWCardLayout) {

                return (SWCardLayout) view;

            }

        }

        return null;

    }


我们先来看下效果。 


我们会发现现在是滚动不了的。那么既然显示都显示出来 了。还怕他滚不了。看老夫随手一段代码分分钟让他滚起来。

那么滚动的处理在那边呢。当然是在onnestprescroll里面。nestedscrolling的一个方法里面处理。这个我前面有介绍过。不了解nestedscrolling可以先翻翻之前的文章。

那么我们应该怎么处理呢?还是老套路,计算偏移量。我们需要先处理自己本身的滑动,在处理其他的滑动来实现联动效果。具体代码如下:

//为了处理联动

    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, SWCardLayout child,

                                  View target, int dx, int dy, int[] consumed) {

        //先处理自己的

        int min = childheight;

        int max = childheight + child.getHeight() - child.getTitleheight();

        int top = child.getTop();

        int offset = Math.min(Math.max(top - dy, min), max) - top;

        child.offsetTopAndBottom(offset);

        consumed[1] = -offset;

        Log.i("----------", "onNestedPreScroll: " + consumed[1]);

        //再处理其他的

        if (consumed[1] == 0) {

            return;

        } else if (consumed[1] > 0) {//上滑

            SWCardLayout current = child;

            SWCardLayout cardLayout = getFrontChild(coordinatorLayout, child);

            while (cardLayout != null) {

                int layoutoffset = getHeightOffset(cardLayout, current);

                cardLayout.offsetTopAndBottom(-layoutoffset);

                current = cardLayout;

                cardLayout = getFrontChild(coordinatorLayout, current);

            }

        } else if (consumed[1] < 0) {//下滑

            SWCardLayout current = child;

            SWCardLayout cardLayout = getNextChild(coordinatorLayout, child);

            while (cardLayout != null) {

                int layoutoffset = getHeightOffset(current, cardLayout);

                cardLayout.offsetTopAndBottom(layoutoffset);

                current = cardLayout;

                cardLayout = getNextChild(coordinatorLayout, current);

            }

        }

    }


上滑的时候我们需要得到前一个的swcardlayout带着他滚动,下滑的时候我们需要得到下一个child进行滚动,得到下一个child的方法其实他之前得到前一个的方法是差不多的。代码如下:


 private SWCardLayout getNextChild(CoordinatorLayout parent, SWCardLayout child) {

        int index = parent.indexOfChild(child);

        for (int i = index + 1; i < parent.getChildCount(); i++) {

            View view = parent.getChildAt(i);

            if (view instanceof SWCardLayout) {

                return (SWCardLayout) view;

            }

        }

        return null;

    }


那么就这么写完了。我们先来看下效果: 

额。。。似乎翻车了。不过就看着图,我们知道应该是偏移量的问题搞错了。我们的偏移量必须要大于0应该。那么我们修改下代码。改成如下的:

else if (consumed[1] > 0) {//上滑

            SWCardLayout current = child;

            SWCardLayout cardLayout = getFrontChild(coordinatorLayout, child);

            while (cardLayout != null) {

                int layoutoffset = getHeightOffset(cardLayout, current);

                if (layoutoffset > 0) {

                    cardLayout.offsetTopAndBottom(-layoutoffset);

                }

                current = cardLayout;

                cardLayout = getFrontChild(coordinatorLayout, current);

            }

        } else if (consumed[1] < 0) {//下滑

            SWCardLayout current = child;

            SWCardLayout cardLayout = getNextChild(coordinatorLayout, child);

            while (cardLayout != null) {

                int layoutoffset = getHeightOffset(current, cardLayout);

                if (layoutoffset > 0) {

                    cardLayout.offsetTopAndBottom(layoutoffset);

                }

                current = cardLayout;

                cardLayout = getNextChild(coordinatorLayout, current);

            }

        }



判断下偏移量的值在做child的offset设置。我们在来看一下效果。 

代码

一个成功的联动效果就这么出来了。最后我放出自定义behavior的整体代码:


public class CardBehavior extends Behavior<SWCardLayout> {


    private int childheight = 0;


    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, SWCardLayout child,

                                       View directTargetChild, View target, int nestedScrollAxes) {

        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 && directTargetChild == child;

    }



    //为了处理联动

    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, SWCardLayout child,

                                  View target, int dx, int dy, int[] consumed) {

        //先处理自己的

        int min = childheight;

        int max = childheight + child.getHeight() - child.getTitleheight();

        int top = child.getTop();

        int offset = Math.min(Math.max(top - dy, min), max) - top;

        child.offsetTopAndBottom(offset);

        consumed[1] = -offset;

        Log.i("----------", "onNestedPreScroll: " + consumed[1]);

        //再处理其他的

        if (consumed[1] == 0) {

            return;

        } else if (consumed[1] > 0) {//上滑

            SWCardLayout current = child;

            SWCardLayout cardLayout = getFrontChild(coordinatorLayout, child);

            while (cardLayout != null) {

                int layoutoffset = getHeightOffset(cardLayout, current);

                if (layoutoffset > 0) {

                    cardLayout.offsetTopAndBottom(-layoutoffset);

                }

                current = cardLayout;

                cardLayout = getFrontChild(coordinatorLayout, current);

            }

        } else if (consumed[1] < 0) {//下滑

            SWCardLayout current = child;

            SWCardLayout cardLayout = getNextChild(coordinatorLayout, child);

            while (cardLayout != null) {

                int layoutoffset = getHeightOffset(current, cardLayout);

                if (layoutoffset > 0) {

                    cardLayout.offsetTopAndBottom(layoutoffset);

                }

                current = cardLayout;

                cardLayout = getNextChild(coordinatorLayout, current);

            }

        }

    }


    private SWCardLayout getNextChild(CoordinatorLayout parent, SWCardLayout child) {

        int index = parent.indexOfChild(child);

        for (int i = index + 1; i < parent.getChildCount(); i++) {

            View view = parent.getChildAt(i);


public class CardBehavior extends Behavior<SWCardLayout> {


    private int childheight = 0;


    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, SWCardLayout child,

                                       View directTargetChild, View target, int nestedScrollAxes) {

        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 && directTargetChild == child;

    }



    //为了处理联动

    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, SWCardLayout child,

                                  View target, int dx, int dy, int[] consumed) {

        //先处理自己的

        int min = childheight;

        int max = childheight + child.getHeight() - child.getTitleheight();

        int top = child.getTop();

        int offset = Math.min(Math.max(top - dy, min), max) - top;

        child.offsetTopAndBottom(offset);

        consumed[1] = -offset;

        Log.i("----------", "onNestedPreScroll: " + consumed[1]);

        //再处理其他的

        if (consumed[1] == 0) {

            return;

        } else if (consumed[1] > 0) {//上滑

            SWCardLayout current = child;

            SWCardLayout cardLayout = getFrontChild(coordinatorLayout, child);

            while (cardLayout != null) {

                int layoutoffset = getHeightOffset(cardLayout, current);

                if (layoutoffset > 0) {

                    cardLayout.offsetTopAndBottom(-layoutoffset);

                }

                current = cardLayout;

                cardLayout = getFrontChild(coordinatorLayout, current);

            }

        } else if (consumed[1] < 0) {//下滑

            SWCardLayout current = child;

            SWCardLayout cardLayout = getNextChild(coordinatorLayout, child);

            while (cardLayout != null) {

                int layoutoffset = getHeightOffset(current, cardLayout);

                if (layoutoffset > 0) {

                    cardLayout.offsetTopAndBottom(layoutoffset);

                }

                current = cardLayout;

                cardLayout = getNextChild(coordinatorLayout, current);

            }

        }

    }


    private SWCardLayout getNextChild(CoordinatorLayout parent, SWCardLayout child) {

        int index = parent.indexOfChild(child);

        for (int i = index + 1; i < parent.getChildCount(); i++) {

            View view = parent.getChildAt(i);

            if (view instanceof SWCardLayout) {

                return (SWCardLayout) view;

            }

        }

        return null;

    }


    private int getHeightOffset(SWCardLayout top, SWCardLayout bottom) {

        return top.getTop() + top.getTitleheight() - bottom.getTop();

    }


    //控制子控件的onlayout和onmesure

    @Override

    public boolean onLayoutChild(CoordinatorLayout parent, SWCardLayout child, int layoutDirection) {

        //按照默认的情况控制,会导致child重叠

        parent.onLayoutChild(child, layoutDirection);

        //控制顶部的偏移量(上一个距离顶部的边距+自身title高度)

        SWCardLayout frontChild = getFrontChild(parent, child);

        if (frontChild != null) {

            int offset = frontChild.getTop() + frontChild.getTitleheight();

            child.offsetTopAndBottom(offset);

        }

        childheight = child.getTop();

        return true;

    }


    //得到前一个child

    private SWCardLayout getFrontChild(CoordinatorLayout parent, SWCardLayout child) {

        int index = parent.indexOfChild(child);

        for (int i = index - 1; i >= 0; i--) {

            View view = parent.getChildAt(i);

            if (view instanceof SWCardLayout) {

                return (SWCardLayout) view;

            }

        }

        return null;

    }


    @Override

    public boolean onMeasureChild(CoordinatorLayout parent, SWCardLayout child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {

        //防止布局的重绘

        int offset = getChildOffset(parent, child);

        int height = View.MeasureSpec.getSize(parentHeightMeasureSpec) - offset;

        child.measure(parentWidthMeasureSpec, View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));

        return true;

    }


    private int getChildOffset(CoordinatorLayout parent, SWCardLayout child) {

        int offset = 0;

        for (int i = 0; i < parent.getChildCount(); i++) {

            View view = parent.getChildAt(i);

            if (view != child) {

                if (view instanceof SWCardLayout) {

                    offset += ((SWCardLayout) view).getTitleheight();

                }

            }

        }

        return offset;

    }

}


当然那个SWCardLayout你们可以自己进行封装。想怎么玩怎么玩~。就是这么6。



文章来源:马云飞博客  

网址:http://blog.csdn.net/sw950729/article/details/75007578


公众号内直接回复“进阶”获取超实用电子

回复“学习”获取海量教学视频