Android自定义控件:NestedScrolling实现仿魅族flyme6应用市场应用详情弹出式layout

来源:互联网 发布:网络带来的利与弊论文 编辑:程序博客网 时间:2024/06/05 17:22

在前一篇博文中已经实现过一个仿魅族flyme6应用市场应用详情弹出式layout: Android自定义控件:从零开始实现魅族flyme6应用市场应用详情弹出式layout,主要是通过viewDragHelper来实现,大部分效果算是实现了,但是在最后还是有一些bug。
趁着这段时间工作比较轻松一点,这次再通过NestedScrolling来实现一次这个自定义控件,对比前面的实现方法,通过NestedScrolling实现起来会简单许多。
老规矩,先看看最终要实现的效果图:

(最终效果图)

NestedScrolling

NestedScrolling是个啥玩意呢?这是Google官方从5.0后引入的滑动嵌套解决方案。
看效果图看的出来,这次我们要实现的效果的难点就在嵌套滑动,因为手指放到scrollview中,然后实际滚动的是却外部的ViewGroup,在ViewGroup滚动到顶部的时候呢,内部的Scrollview又继续滚动。按照传统的View事件拦截和处理方式,那首先要保证ViewGroup拦截事件,否则事件会被内部的scrollview消费掉。但是如果拦截了,当ViewGroup滚动到顶部的时候又如何让scrollview又持续滑动呢?按照传统的方式,一次事件拦截就是一次性处理的事情,ViewGroup如果拦截了这次滑动事件,那么scrollview肯定是没法继续处理这次滑动事件的。
我们上篇博文是通过事件拦截和分发人为的在ViewGroup中更动态的修改scrollView的滑动,从视觉上实现一次滑动事件ViewGroup和子view嵌套的滚动效果。实际上从本质上来讲,还是ViewGroup拦截和消费了事件,第一次ViewGroup中的事件并没有到子view中去处理。

那么NestedScrolling如何实现嵌套滑动呢?
NestedScrollingParent内部实现了NestedScrollingChild接口的子View会优先获得事件处理权,然后滑动的时候,会先将dx、dy传入给NestedScrollingParent,NestedScrollingParent可以决定是否对其进行消耗,也就是说NestedScrollingParent可以消费部分dx、dy,余下的未消费完的dx、dy交还给子view去消费。

这样看实际上要实现本次的效果就很简单了,话不多说,贴代码。

先让我们的自定义ScrollView实现NestedScrollingChild接口,并且将NestedScrolling相关的处理全部交给ScrollingChildHelper处理。

public class MyScrollView extends ScrollView implements NestedScrollingChild{    private boolean isScrollToTop = true;    private boolean isScrollToBottom = false;    private OnScrollLimitListener mOnScrollLimitListener;    private NestedScrollingChildHelper mScrollingChildHelper;    public MyScrollView(Context context) {        this(context, null);    }    public MyScrollView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public void setNestedScrollingEnabled(boolean enabled) {        getScrollingChildHelper().setNestedScrollingEnabled(enabled);    }    @Override    public boolean isNestedScrollingEnabled() {        return getScrollingChildHelper().isNestedScrollingEnabled();    }    @Override    public boolean startNestedScroll(int axes) {        return getScrollingChildHelper().startNestedScroll(axes);    }    @Override    public void stopNestedScroll() {        getScrollingChildHelper().stopNestedScroll();    }    @Override    public boolean hasNestedScrollingParent() {        return getScrollingChildHelper().hasNestedScrollingParent();    }    @Override    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,                                        int dyUnconsumed, int[] offsetInWindow) {        return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,                dxUnconsumed, dyUnconsumed, offsetInWindow);    }    @Override    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {        return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);    }    @Override    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {        return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);    }    @Override    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {        return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);    }    private NestedScrollingChildHelper getScrollingChildHelper() {        if (mScrollingChildHelper == null) {            mScrollingChildHelper = new NestedScrollingChildHelper(this);            setNestedScrollingEnabled(true);        }        return mScrollingChildHelper;    }    /**     * 设置ScrollView滑动到边界监听     *     * @param onScrollLimitListener ScrollView滑动到边界监听     */    public void setOnScrollLimitListener(OnScrollLimitListener onScrollLimitListener) {        mOnScrollLimitListener = onScrollLimitListener;    }    @Override    protected void onScrollChanged(int l, int t, int oldl, int oldt) {        super.onScrollChanged(l, t, oldl, oldt);        if (getScrollY() == 0) {//滑动到顶部            isScrollToTop = true;            isScrollToBottom = false;            isScrollToBottom = false;        } else if (getScrollY() + getHeight() - getPaddingTop() - getPaddingBottom() ==                getChildAt(0).getHeight()) {            // 小心踩坑: 这里不能是 >=            // 小心踩坑:这里最容易忽视的就是ScrollView上下的padding             isScrollToTop = false;            isScrollToBottom = true;        } else {            isScrollToTop = false;            isScrollToBottom = false;        }        notifyScrollChangedListeners();    }    /**     * 回调     */    private void notifyScrollChangedListeners() {        if (isScrollToTop) {            if (mOnScrollLimitListener != null) {                mOnScrollLimitListener.onScrollTop();            }        } else if (isScrollToBottom) {            if (mOnScrollLimitListener != null) {                mOnScrollLimitListener.onScrollBottom();            }        } else {            if (mOnScrollLimitListener != null) {                mOnScrollLimitListener.onScrollOther();            }        }    }    /**     * scrollview滑动到边界监听接口     */    public interface OnScrollLimitListener {        /**         * 滑动到顶部         */        void onScrollTop();        /**         * 滑动到顶部和底部之间的位置(既不是顶部也不是底部)         */        void onScrollOther();        /**         * 滑动到底部         */        void onScrollBottom();    }}

然后是我们的PopupLayout,上一篇博文是通过自定义FrameLayout的方式实现的,这次由于是通过NestedScrolling实现,所以一次滑动事件其实是针对整个ViewGroup的,所以本次采取自定义LinearLayout的方式去实现。

在这里我们重点看下面几个方法,首先是onMeasure方法。因为初始状态下ContentView是在界面之外的,所以要确定ContentView的高度。

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        Log.e("tag", "onMeasure");        ViewGroup.LayoutParams params = contentView.getLayoutParams();        params.height = darkView.getMeasuredHeight() - mOrginY;        setMeasuredDimension(getMeasuredWidth(), contentView.getMeasuredHeight() + darkView                .getMeasuredHeight());    }

接下来看看重写的NestedScrollingParent几个方法。

@Override    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {        Log.e(TAG, "onStartNestedScroll");        return true;    }    @Override    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {        Log.e(TAG, "onNestedScrollAccepted");    }    @Override    public void onStopNestedScroll(View target) {        Log.e(TAG, "onStopNestedScroll");        if (mDarkViewHeight - mOrginY - getScrollY() > mDragRange) {//向下拖拽,超出拖拽限定距离            dismiss();        } else if (mDarkViewHeight - mOrginY - getScrollY() > 0) {//向下拖拽,但是没有超出拖拽限定距离            springback();        }    }    @Override    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int            dyUnconsumed) {        Log.e(TAG, "onNestedScroll");    }    @Override    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {        boolean patchDown = dy < 0 && mIsScrollInTop;//下滑        boolean patchUp = dy > 0 && getScrollY() < (mDarkViewHeight - UIUtils.getStatusBarHeight                (target));//上滑        if (patchDown || patchUp) {            scrollBy(0, dy);            consumed[1] = dy;        }    }    @Override    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {        return true;    }    @Override    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {        //不做拦截 可以传递给子View        return false;    }    @Override    public int getNestedScrollAxes() {        Log.e(TAG, "getNestedScrollAxes");        return 0;    }

onNestedPreScroll中,我们判断,如果是上滑且contentView未滑动到顶部,则消耗掉dy,即consumed[1]=dy。如果是下滑且内部scrollview已经滑动到顶,则消耗掉dy,即consumed[1]=dy,消耗掉的意思,就是自己去执行scrollBy,实际上就是滑动PopupLayout本身。

onStopNestedScroll中,我们判断向下滑动的距离,来确定是dismiss PopupLayout还是回弹到初始位置。

最后由于需要更新TitleBar的状态,所以重写了scrollTo方法,在scrollTo方法中更新TitleBar的状态。

    @Override    public void scrollTo(int x, int y) {        if (y >= mDarkViewHeight - UIUtils.getStatusBarHeight(this)) {            y = mDarkViewHeight - UIUtils.getStatusBarHeight(this);            darkView.setBackgroundColor(Color.WHITE);//拖动到顶部时darkview背景设置白色            titleBar.setBackImageResource(R.mipmap.back);        } else {            darkView.setBackgroundResource(R.color.dark);//没有拖动到顶部时darkview背景设置暗色            titleBar.setBackImageResource(R.mipmap.close);        }        if (y != getScrollY()) {            super.scrollTo(x, y);        }    }

本次的要点基本就这么多,总的来说相较上一篇博文各种绞尽脑汁想着事件处理,这次通过NestedScrolling就重写几个方法,然后根据自己的实际需求做一些判断,实现起来还是很简单的。

最后附上源码链接:https://github.com/Horrarndoo/PopupLayoutNew

阅读全文
0 0
原创粉丝点击