自定义AppBarLayout,让它Fling起来更流畅

栏目: IOS · Android · 发布时间: 5年前

内容简介:我们知道,Desgin包中的AppBarLayout配合CollapsingToolbarLayout可以实现折叠效果。但是顶部在快速滑动到折叠状态时,底部的NestedScrollChild不会因为惯性跟着滑动,整个滑动过程瞬间停止,给人一种很不流畅的感觉。为了能让我们的AppBarLayout能Fling更流畅,我们需要在重新修改源码,定制一个FlingAppBarLayout,能够实现类似饿了么首页效果我们知道AppBarLayout之所以能够有折叠效果,是因为有一个默认的Behavior,而且App

我们知道,Desgin包中的AppBarLayout配合CollapsingToolbarLayout可以实现折叠效果。但是顶部在快速滑动到折叠状态时,底部的NestedScrollChild不会因为惯性跟着滑动,整个滑动过程瞬间停止,给人一种很不流畅的感觉。为了能让我们的AppBarLayout能Fling更流畅,我们需要在重新修改源码,定制一个FlingAppBarLayout,能够实现类似饿了么首页效果

自定义AppBarLayout,让它Fling起来更流畅

思路

我们知道AppBarLayout之所以能够有折叠效果,是因为有一个默认的Behavior,而且AppBarLayout在快速滑动时,布局也能够快速展开和收缩,因此可以猜测内部有可能处理了Fling事件。通过源码,找到对应的Behavior,它继承自HeaderBehavior,通过onTouchEvent方法,找到了对应对于Fling事件的处理

case MotionEvent.ACTION_UP:
                if (mVelocityTracker != null) {
                    mVelocityTracker.addMovement(ev);
                    mVelocityTracker.computeCurrentVelocity(1000);
                    float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
                    fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel);
                }
复制代码

进入fling方法,找到了scroller对象,AppBarLayout的快速滑动效果就是通过它来实现的。至于为什么AppBarLayout向上快速滑动到边界时,突然停止,没有惯性滑动,是因为scroller在调用fling方法时设置了minOffset(向上滑动边界)

final boolean fling(CoordinatorLayout coordinatorLayout, V layout, int minOffset,
            int maxOffset, float velocityY) {
        if (mFlingRunnable != null) {
            layout.removeCallbacks(mFlingRunnable);
            mFlingRunnable = null;
        }

        if (mScroller == null) {
            mScroller = new OverScroller(layout.getContext());
        }

        mScroller.fling(
                0, getTopAndBottomOffset(), // curr
                0, Math.round(velocityY), // velocity.
                0, 0, // x
                minOffset, maxOffset); // y

        if (mScroller.computeScrollOffset()) {
            mFlingRunnable = new FlingRunnable(coordinatorLayout, layout);
            ViewCompat.postOnAnimation(layout, mFlingRunnable);
            return true;
        } else {
            onFlingFinished(coordinatorLayout, layout);
            return false;
        }
    }
复制代码

而具体的view的移动,则是通过FlingRunnable来实现。

private class FlingRunnable implements Runnable {
        private final CoordinatorLayout mParent;
        private final V mLayout;

        FlingRunnable(CoordinatorLayout parent, V layout) {
            mParent = parent;
            mLayout = layout;
        }

        @Override
        public void run() {
            if (mLayout != null && mScroller != null) {
                if (mScroller.computeScrollOffset()) {
                    setHeaderTopBottomOffset(mParent, mLayout, mScroller.getCurrY());
                    // Post ourselves so that we run on the next animation
                    ViewCompat.postOnAnimation(mLayout, this);
                } else {
                    onFlingFinished(mParent, mLayout);
                }
            }
        }
    }
复制代码

通过这个FlingRunnable类,我们知道AppBarLayout能快速展开和收缩,就是通过它实现的。

具体实现

首先,我们把design中的AppBarLayout源码复制到自己的package中,引入报红的相关文件,具体如下:

自定义AppBarLayout,让它Fling起来更流畅

其中ScrollItem,ReflectUtil,ViewPagerUtil为我们自己定义的,其他都是design包拷贝的。 通过前面三块代码,我们知道AppBarLayout的Fling效果是通过scroller实现的,滑动的边界时通过minOffset和maxOffset来控制的,当滑动的offset超出范围时,scroller调用computeScrollerOffset就为false,顶部view就停止移动了。

因此为了能让AppBarLayout在向上滑动到minOffset边界时不停止移动,把这个minOffset保存到FlingRunnable中,在scroller.fling方法中这个更小的offset,这个在滑动到minOffset时,computeScrollerOffset就不会为false,并且在FlingRunnable中因为有minOffset,我们可以在mScroller.computeScrollOffset里判断是否滑出边界,通过差值,继续滑动底部的可滑动布局。

mScroller.fling(
                0, getTopAndBottomOffset(), // curr
                0, Math.round(velocityY), // velocity.
                0, 0, // x
                minOffset-5000, maxOffset); // 设置一个很大的值,在向上滑动时不会因为低于minOffset而停止滑动
复制代码

在FlingRunnable中新增minOffset字段,run方法中,如果currY<minOffset表示AppBarLayout向上滑动值收缩状态,可以滑动底部布局了,scrollNext(),传入偏移量minOffset-currY

class FlingRunnable implements Runnable {
        private final CoordinatorLayout mParent;
        private final V mLayout;
        private int minOffset;

        FlingRunnable(CoordinatorLayout parent, V layout, int min) {
            mParent = parent;
            mLayout = layout;
            minOffset = min;
        }
        @Override
        public void run() {
            if (mLayout != null && mScroller != null) {
                if (mScroller.computeScrollOffset()) {
                    int currY = mScroller.getCurrY();
                    if (currY < 0 && currY < minOffset) {
                        scrollNext(minOffset - currY);
                        setHeaderTopBottomOffset(mParent, mLayout, minOffset);
                    } else {
                        setHeaderTopBottomOffset(mParent, mLayout, currY);
                    }
                    // Post ourselves so that we run on the next animation
                    ViewCompat.postOnAnimation(mLayout, this);
                } else {
                    onFlingFinished(mParent, mLayout);
                }
            }
        }
    }
复制代码

在构造FlingRunnable时传入minOffset

final boolean fling(CoordinatorLayout coordinatorLayout, V layout, int minOffset,
                        int maxOffset, float velocityY) {
        if (mFlingRunnable != null) {
            layout.removeCallbacks(mFlingRunnable);
            mFlingRunnable = null;
        }
        if (mScroller == null) {
            mScroller = new OverScroller(layout.getContext());
        }
        mScroller.fling(
                0, getTopAndBottomOffset(), // curr
                0, Math.round(velocityY), // velocity.
                0, 0, // x
                minOffset-5000, maxOffset); // y

        if (mScroller.computeScrollOffset()) {
            mFlingRunnable = new FlingRunnable(coordinatorLayout, layout, minOffset);
            ViewCompat.postOnAnimation(layout, mFlingRunnable);
            return true;
        } else {
           ...
        }
    }
复制代码

接着就是具体scrollNext方法了,具体就是找到底部的NestedScrollingChild(如RecyclerView,NestedScrollView,ViewPager,主要是这三个)。 在FlingRunnable中新增ScrollItem字段用于处理scroll逻辑

class FlingRunnable implements Runnable {
        private final CoordinatorLayout mParent;
        private final V mLayout;
        private int minOffset;
        private ScrollItem scrollItem;


        FlingRunnable(CoordinatorLayout parent, V layout, int min) {
            mParent = parent;
            mLayout = layout;
            minOffset = min;
            initNextScrollView(parent);
        }

        private void initNextScrollView(CoordinatorLayout parent) {
            int count = parent.getChildCount();
            for (int i = 0; i < count; i++) {
                View v = parent.getChildAt(i);
                CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) v.getLayoutParams();
                if (lp.getBehavior() instanceof AppBarLayout.ScrollingViewBehavior) {
                    scrollItem = new ScrollItem(v);
                }
            }
        @Override
        public void run() {
            if (mLayout != null && mScroller != null) {
                if (mScroller.computeScrollOffset()) {
                    int currY = mScroller.getCurrY();
                    if (currY < 0 && currY < minOffset) {
                        scrollItem.scroll(minOffset - currY); //处理逻辑在ScrollItem中
                        setHeaderTopBottomOffset(mParent, mLayout, minOffset);
                    } else {
                        setHeaderTopBottomOffset(mParent, mLayout, currY);
                    }
                    // Post ourselves so that we run on the next animation
                    ViewCompat.postOnAnimation(mLayout, this);
                } else {
                    onFlingFinished(mParent, mLayout);
                }
            }
        }
}
复制代码

而在新增的ScrollItem中,我们来处理对应的scroll操作(NestedScrollView可以通过scrollTo,而RecyclerView则需要用LinearLayoutManager来控制了)

public class ScrollItem {
    private int type; //1: NestedScrollView   2:RecyclerView
    private WeakReference<NestedScrollView> scrollViewRef;
    private WeakReference<LinearLayoutManager> layoutManagerRef;

    public ScrollItem(View v) {
        findScrollItem(v);
    }

    /**
     * 查找需要滑动的scroll对象
     *
     * @param v
     */
    protected boolean findScrollItem(View v) {
        if (findCommonScroll(v)) return true;
        if (v instanceof ViewPager) {
            View root = ViewPagerUtil.findCurrent((ViewPager) v);
            if (root != null) {
                View child = root.findViewWithTag("fling");
                return findCommonScroll(child);
            }
        }
        return false;
    }

    private boolean findCommonScroll(View v) {
        if (v instanceof NestedScrollView) {
            type = 1;
            scrollViewRef = new WeakReference<NestedScrollView>((NestedScrollView) v);
            stopScroll(scrollViewRef.get());
            return true;
        }
        if (v instanceof RecyclerView) {
            RecyclerView.LayoutManager lm = ((RecyclerView) v).getLayoutManager();
            if (lm instanceof LinearLayoutManager) {
                LinearLayoutManager llm = (LinearLayoutManager) lm;
                type = 2;
                layoutManagerRef = new WeakReference<LinearLayoutManager>(llm);
                stopScroll((RecyclerView) v);
                return true;
            }
        }
        return false;
    }

    /**
     * 停止NestedScrollView滚动
     *
     * @param v
     */
    private void stopScroll(NestedScrollView v) {
        try {
            Field field = ReflectUtil.getDeclaredField(v, "mScroller");
            if (field == null) return;
            field.setAccessible(true);
            OverScroller scroller = (OverScroller) field.get(v);
            if (scroller != null) scroller.abortAnimation();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 停止RecyclerView滚动
     *
     * @param
     */
    private void stopScroll(RecyclerView rv) {
        try {
            Field field = ReflectUtil.getDeclaredField(rv, "mViewFlinger");
            if (field == null) return;
            field.setAccessible(true);
            Object obj = field.get(rv);
            if (obj == null) return;
            Method method = obj.getClass().getDeclaredMethod("stop");
            method.setAccessible(true);
            method.invoke(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void scroll(int dy) {
        if (type == 1) {
            scrollViewRef.get().scrollTo(0, dy);
        } else if (type == 2) {
            layoutManagerRef.get().scrollToPositionWithOffset(0, -dy);
        }
    }

}
复制代码

至于ViewPager,因为getChildAt会有空值问题,这里是通过adapter获取fragment然后获取rootView做处理

public class ViewPagerUtil {
    public static View findCurrent(ViewPager vp) {
        int position = vp.getCurrentItem();
        PagerAdapter adapter = vp.getAdapter();
        if (adapter instanceof FragmentStatePagerAdapter) {
            FragmentStatePagerAdapter fsp = (FragmentStatePagerAdapter) adapter;
            return fsp.getItem(position).getView();
        } else if (adapter instanceof FragmentPagerAdapter) {
            FragmentPagerAdapter fp = (FragmentPagerAdapter) adapter;
            return fp.getItem(position).getView();
        }
        return null;
    }
}
复制代码

这里暂时没做PagerAdapter的处理逻辑,ViewPager找到当前item界面rootView后,需要找到需要继续惯性滑动到RecyclerView或NestedScrollView,为方便查找,我们给fragment布局中需要滑动的组件添加tag:“fling”,这样就可以通过findViewWithTag("fling")找到它。 好了,基本的滑动逻辑处理完了,我们自己的AppBarLayout可以惯性fling了。会看ScrollItem代码,我加了stopScroll的逻辑。那是因为在底部recyclerView或NestedScrollView快速向下滑动至AppBarLayout展开,而这时在AppBarLayout想要快速向上滑动,应为底部正在滑动,导致两者冲突,不能正常向上滑动,所以AppBarLayout在向上快速滑动时,要停止底部滑动。通过NestedScrollView和RecyclerView的源码,我们找到控制滑动逻辑的OverScroller和ViewFlinger,我们可以通过反射来停止对应的滑动。


以上所述就是小编给大家介绍的《自定义AppBarLayout,让它Fling起来更流畅》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ethnography and Virtual Worlds

Ethnography and Virtual Worlds

Tom Boellstorff、Bonnie Nardi、Celia Pearce、T. L. Taylor / Princeton University Press / 2012-9-16 / GBP 21.00

"Ethnography and Virtual Worlds" is the only book of its kind - a concise, comprehensive, and practical guide for students, teachers, designers, and scholars interested in using ethnographic methods t......一起来看看 《Ethnography and Virtual Worlds》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具