Android中NestedScrollingParent嵌套ListView

来源:互联网 发布:蛋鸡存栏量数据 编辑:程序博客网 时间:2024/05/18 04:25


要实现这种效果,使用CoordinatorLayout,AppBarLayout,RecyclerView很容易就能完成。由于当前开发的工程由于一些原因不能使用AndroidDesignSupport包。只能自己解决滑动嵌套问题,实现了这个功能,顺便学习了下NestedScrollingParent,NestedScrollingChild的用法。如果想结合代码看文章源码链接在底部。

简单的说下NestedScrollingParent,NestedScrollingChild就是两个接口,在新的android.support.v4包中,两个接口定义了一些操作,然后通过NestedScrollingChildHelper把两者联系起来。

在子View中要联动滚动之前需要调用startNestedScroll(),这个时候NestedScrollingChildHelper中就会向父View寻找实现了NestedScrollingParent的View,并把他保存起来。当滑动事件传递到子View的时候,子View一般要去询问父View是否要滚动,然后方法返回后子View在决定自身是否要滚动。
子View可以通过传给helper的consumed,offsetInWindow数组得到父View消耗的距离,与自身在屏幕的偏移距离。这样子View根据父View的返回在决定自己是否滚动。大概调用关系如下图。
这里写图片描述

动画中的View布局关系如下图,先滚动1(也有可能不滚动),在滚动2.
这里写图片描述

这里是NestedScrollParentLayout的简单实现,调用scrollBy方法滚动

public class NestedScrollParentLayout extends RelativeLayout implements NestedScrollingParent {    private NestedScrollingParentHelper mParentHelper;    private int mTitleHeight;    private View mTitleTabView;    public NestedScrollParentLayout(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public NestedScrollParentLayout(Context context) {        super(context);        init();    }    private void init() {        mParentHelper = new NestedScrollingParentHelper(this);    }    //获取子view    @Override    protected void onFinishInflate() {        mTitleTabView = this.findViewById(R.id.title_input_container);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        mTitleHeight = mTitleTabView.getMeasuredHeight();        super.onMeasure(widthMeasureSpec, heightMeasureSpec + mTitleHeight);    }    //接口实现--------------------------------------------------    //在此可以判断参数target是哪一个子view以及滚动的方向,然后决定是否要配合其进行嵌套滚动    @Override    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {        if (target instanceof NestedListView) {            return true;        }        return false;    }    @Override    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {        mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);    }    @Override    public void onStopNestedScroll(View target) {        mParentHelper.onStopNestedScroll(target);    }    //先于child滚动    //前3个为输入参数,最后一个是输出参数    @Override    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {        if (dy > 0) {//手势向上滑动            if (getScrollY() < mTitleHeight) {                scrollBy(0, dy);//滚动                consumed[1] = dy;//告诉child我消费了多少            }        } else if (dy < 0) {//手势向下滑动            if (getScrollY() > 0) {                scrollBy(0, dy);//滚动                consumed[1] = dy;//告诉child我消费了多少            }        }    }    //后于child滚动    @Override    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {    }    //返回值:是否消费了fling    @Override    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {        return false;    }    //返回值:是否消费了fling    @Override    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {//        if (!consumed) {//            return true;//        }        return false;    }    @Override    public int getNestedScrollAxes() {        return mParentHelper.getNestedScrollAxes();    }    //scrollBy内部会调用scrollTo    //限制滚动范围    @Override    public void scrollTo(int x, int y) {        if (y < 0) {            y = 0;        }        if (y > mTitleHeight) {            y = mTitleHeight;        }        super.scrollTo(x, y);    }}

这里是NestedListView的实现,onTouchEvent部分代码主要来自RecyclerView的onTouchEvent中

public class NestedListView extends ListView implements NestedScrollingChild {    private NestedScrollingChildHelper mChildHelper;    private int[] mNestedOffsets = new int[2];    private int[] mScrollConsumed = new int[2];    private int[] mScrollOffset = new int[2];    private int mScrollPointerId;    private int mLastTouchX;    private int mLastTouchY;    private final static String TAG = "NestedListView";    public NestedListView(Context context) {        super(context);        init();    }    public NestedListView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public NestedListView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    public NestedListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);    }    private void init() {        mChildHelper = new NestedScrollingChildHelper(this);        setNestedScrollingEnabled(true);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }    private boolean isFirst = true;//DOWN事件没执行暂时    private int lastDy;//暂时解决第一次MOVE与后序符号相反,导致的抖动问题    @Override    public boolean onTouchEvent(MotionEvent e) {        //下述代码主要复制于RecyclerView        final MotionEvent vtev = MotionEvent.obtain(e);        final int action = MotionEventCompat.getActionMasked(e);        final int actionIndex = MotionEventCompat.getActionIndex(e);        if (action == MotionEvent.ACTION_DOWN) {            mNestedOffsets[0] = mNestedOffsets[1] = 0;        }        vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);        switch (action) {            case MotionEvent.ACTION_DOWN: {                //不知道为啥没有执行                resetScroll(e);            }            break;            case MotionEventCompat.ACTION_POINTER_DOWN: {                mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);                mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);                mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);            }            break;            case MotionEvent.ACTION_MOVE: {                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);                if (index < 0) {                    Log.e(TAG, "Error processing scroll; pointer index for id " +                            mScrollPointerId + " not found. Did any MotionEvents get skipped?");                    return false;                }                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);                int dx = mLastTouchX - x;                int dy = mLastTouchY - y;                if (isFirst) {//暂时解决第次dy与后序符号相反导致的闪动问题                    Log.i("pyt", "FIRST");                    isFirst = false;                    resetScroll(e);                    return true;                }                if (!isSignOpposite(lastDy, dy)) {//解决手机触摸在屏幕上不松开一直抖动的问题                    lastDy = dy;                    Log.i("pyt", "move lastY" + mLastTouchY + ",y=" + y + ",dy=" + dy);                    if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {                        vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);                        // Updated the nested offsets                        mNestedOffsets[0] += mScrollOffset[0];                        mNestedOffsets[1] += mScrollOffset[1];                    }                    mLastTouchX = x - mScrollOffset[0];                    mLastTouchY = y - mScrollOffset[1];                }            }            break;            case MotionEvent.ACTION_UP: {                stopNestedScroll();//                resetTouch();                isFirst = true;            }            break;            case MotionEvent.ACTION_CANCEL: {//                cancelTouch();            }            break;        }        super.onTouchEvent(e);        return true;    }    private void resetScroll(MotionEvent e) {        lastDy = 0;        mNestedOffsets[0] = mNestedOffsets[1] = 0;        mScrollPointerId = MotionEventCompat.getPointerId(e, 0);        mLastTouchX = (int) (e.getX() + 0.5f);        mLastTouchY = (int) (e.getY() + 0.5f);        int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;        nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;        startNestedScroll(nestedScrollAxis);    }    /**     * 判断符号是否相反,可以改成异或     *     * @param f     * @param s     * @return     */    private boolean isSignOpposite(int f, int s) {        if (f > 0 && s < 0 || f < 0 && s > 0) {            return true;        }        return false;    }    //以下为接口实现--------------------------------------------------    @Override    public void setNestedScrollingEnabled(boolean enabled) {        mChildHelper.setNestedScrollingEnabled(enabled);    }    @Override    public boolean isNestedScrollingEnabled() {        return mChildHelper.isNestedScrollingEnabled();    }    @Override    public boolean startNestedScroll(int axes) {        return mChildHelper.startNestedScroll(axes);    }    @Override    public void stopNestedScroll() {        mChildHelper.stopNestedScroll();    }    @Override    public boolean hasNestedScrollingParent() {        return mChildHelper.hasNestedScrollingParent();    }    @Override    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {        return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);    }    @Override    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);    }    @Override    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {        return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);    }    @Override    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {        return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);    }}

代码下载:https://github.com/pengyuntao/NestedListView/

0 0
原创粉丝点击