Ashin相对布局中事件冲突

本文介绍了一种自定义的ScrollView实现方案,包括CustomScrollView和ScrollViewContainer组件的详细代码解析。通过这些组件,可以实现商品详情页面的上下滑动切换效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">

    <com.hopmet.meijiago.ui.widget.ScrollViewContainer
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <include
            android:id="@+id/layout_good_detail_top"
            layout="@layout/include_good_detail_top"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        <include
            layout="@layout/include_good_detail_bottom"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </com.hopmet.meijiago.ui.widget.ScrollViewContainer>
</FrameLayout> 

<?xml version="1.0" encoding="utf-8"?>
<com.hopmet.meijiago.ui.widget.CustomScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:descendantFocusability="blocksDescendants"
    android:fillViewport="true"
    android:orientation="vertical"
    android:scrollbars="none">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v4.view.ViewPager
                android:id="@+id/vp_goods_detail"
                android:layout_width="match_parent"
                android:layout_height="320dp"
                android:flipInterval="1000"
                android:layout_gravity="center"
                android:persistentDrawingCache="animation" />

            <com.hopmet.meijiago.ui.widget.IndicatorLinearLayout
                android:id="@+id/indicator_good_detail"
                android:layout_width="match_parent"
                android:layout_height="20dp"
                android:layout_gravity="bottom"
                android:gravity="center" />

        </FrameLayout>
    </LinearLayout>
</com.hopmet.meijiago.ui.widget.CustomScrollView>


<?xml version="1.0" encoding="utf-8"?>
<com.hopmet.meijiago.ui.widget.CustomScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true"
    android:scrollbars="none">


       <com.hopmet.meijiago.ui.widget.MyWebView
           android:id="@+id/web_view_good_detail"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"/>

</com.hopmet.meijiago.ui.widget.CustomScrollView>


public class CustomScrollView extends ScrollView{

    private View mChildView;

    public CustomScrollView(Context context) {
        super(context);
    }

    public CustomScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public interface OnScrollStoppedListener{
        void onScrollStoppedInTop();
        void onScrollStoppedInBottom();
        void onScrollStillMove();
    }

    private OnScrollStoppedListener onScrollStoppedListener;

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        mChildView = getChildAt(0);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {

        int bottom = mChildView.getBottom();
        bottom -= (getHeight() + getScrollY());

        if(bottom == 0){//滚动到底部
            if(onScrollStoppedListener != null){
                onScrollStoppedListener.onScrollStoppedInBottom();
            }
        }else if(getScrollY() == 0){//滚动到顶部
            if(onScrollStoppedListener != null){
                onScrollStoppedListener.onScrollStoppedInTop();
            }
        }else{
            if(onScrollStoppedListener != null){//在滑动中
                onScrollStoppedListener.onScrollStillMove();
            }
        }
    }

    public void setOnScrollStoppedListener(CustomScrollView.OnScrollStoppedListener listener){
        onScrollStoppedListener = listener;
    }
}


public class ScrollViewContainer extends RelativeLayout {

    private MyWebView myWebView;

    /** 自动上滑 */
    public static final int AUTO_UP = 0;
    /** 自动下滑 */
    public static final int AUTO_DOWN = 1;
    /** 动画完成 */
    public static final int DONE = 2;
    /** 动画速度 */
    public static final float SPEED = 6.5f;
    /** 顶部视图 */
    private static int TOP_VIEW = 0;
    /** 底部视图 */
    private static int BOTTOM_VIEW = 1;


    /** 用于计算手滑动的速度 */
    private VelocityTracker vt;

    private int mViewHeight;
    private int mViewWidth;

    private CustomScrollView topView;
    private CustomScrollView bottomView;

    private int state = DONE;

    /** 记录当前展示的是哪个view0topView1bottomView */
    private int mCurrentPos = TOP_VIEW;

    /** 手滑动距离,这个是控制布局的主要变量 */
    private int mMarginTop;
    private MyTimer mTimer;
    /** 记录最上次的y坐标,在按下和拖动逻辑的最后更新 */
    private float mLastY;

    /**
     * 用于控制是否变动布局的另一个条件,mCanDrag==0时布局可以拖拽了,mCanDrag==-1时可以舍弃将要到来的第一个move事件,
     * 这点是去除多点拖动剧变的关键
     */
    private int mCanDrag;

    private Handler handler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            if (mMarginTop != 0) {
                if (state == AUTO_UP) { // 手指向上滑动时
                    mMarginTop -= SPEED;
                    if (mMarginTop <= -mViewHeight) { // 越界处理
                        mMarginTop = -mViewHeight;
                        state = DONE;
                        mCurrentPos = BOTTOM_VIEW; // 1bottomView
                    }
                } else if (state == AUTO_DOWN) { // 手指向下滑动时
                    mMarginTop += SPEED;
                    if (mMarginTop >= 0) { // 越界处理
                        mMarginTop = 0;
                        state = DONE;
                        mCurrentPos = TOP_VIEW; // 0topView
                    }
                } else {
                    mTimer.cancel();
                }
            } else {
                mTimer.cancel();
            }

            requestLayout();
        }

    };

    public ScrollViewContainer(Context context) {
        super(context);
        init();
    }

    public ScrollViewContainer(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ScrollViewContainer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        mTimer = new MyTimer(handler);
    }

    /** 手指按下去时刻的y坐标, 每次按下的时刻才会更新 */
    private float downY;

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        topView = (CustomScrollView) getChildAt(0);

        bottomView = (CustomScrollView) getChildAt(1);
        myWebView = (MyWebView) bottomView.findViewById(R.id.web_view_good_detail);

        vt = VelocityTracker.obtain();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:

                downY = mLastY = ev.getY();
                vt.addMovement(ev);
                mCanDrag = 0;//布局可以拖拽
                break;

            case MotionEvent.ACTION_POINTER_DOWN:
            case MotionEvent.ACTION_POINTER_UP:
                // 多一只手指按下或抬起时舍弃将要到来的第一个事件move,防止多点拖拽的bug
                mCanDrag = -1;
                break;
            case MotionEvent.ACTION_MOVE:
                // 如果缺少改逻辑就会导致当滑动到WebView时继续向上滑动webview的内容有会上滑,当下拉时webview的内容不会跟着下滑而是直接切换到上个布局中
                if (myWebView.getVerticalScrollOffset() != 0) {
                  
                    myWebView.dispatchTouchEvent(ev);
                    return true;
                }

                int y = (int) ev.getY();
                vt.addMovement(ev);
                float deltaY = downY - y; // 按下后连续的偏移量

                if (mCurrentPos == TOP_VIEW && mCanDrag == 0) { //可上拉,当前展示为topView,可拖拽
                    if(Math.abs(deltaY) > 150){//防止与横向listView的滑动冲突
                        mMarginTop += (ev.getY() - mLastY);
                        adjustMarginTop();
                        if (mMarginTop < -8) {
                            // 防止事件冲突
                            ev.setAction(MotionEvent.ACTION_CANCEL);
                        }
                    }
                } else if (mCurrentPos == BOTTOM_VIEW && mCanDrag == 0) {//可下拉,当前展示为bottomView,可拖拽
                    mMarginTop += (ev.getY() - mLastY);
                    adjustMarginTop();
                    if (mMarginTop > -mViewHeight) {
                        // 防止事件冲突
                        ev.setAction(MotionEvent.ACTION_CANCEL);
                    }
                } else {
                    mCanDrag++;
                }
                mLastY = y;
                // 滑动过程中布局的更新
                requestLayout();
                break;
            case MotionEvent.ACTION_UP:
                vt.addMovement(ev);
                vt.computeCurrentVelocity(700);
                // 获取Y方向的速度
                float mYV = vt.getYVelocity();
                if (mMarginTop == 0 || mMarginTop == -mViewHeight)
                    break;
                if (Math.abs(mYV) < 500) { // 慢速滑动 速度小于一定值的时候当作静止释放,两个View往哪移动取决于滑动的距离
                    setState(mMarginTop <= -mViewHeight / 3);
                } else {  // 快速滑动 抬起手指时速度方向决定两个View往哪移动
                    setState(mYV < 0);
                }
                vt.clear();
                mTimer.schedule(2);
        }

        super.dispatchTouchEvent(ev);
        // 后续事件(ACTION_MOVEACTION_UP)会再传递进来
        return true;
    }

    /**
     *
     * @param up  true 向上滑动 false 向下滑动
     */
    private void setState(boolean up) {
        if (up) {
            state = AUTO_UP;
        } else {
            state = AUTO_DOWN;
        }
    }

    /**
     * 调整mMarginTop的值
     */
    private void adjustMarginTop() {
        // 防止上下越界
        if (mMarginTop > 0) {//上边界为0
            mMarginTop = 0;
            mCurrentPos = TOP_VIEW;
        } else if (mMarginTop < -mViewHeight) { //下边界为-mViewHeight
            mMarginTop = -mViewHeight;
            mCurrentPos = BOTTOM_VIEW;
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        // 手指向顶部滑动时,mMarginTop的值负的越来越大,最终达到最小值

        topView.layout(0, mMarginTop, mViewWidth, topView.getMeasuredHeight() + mMarginTop);

        bottomView.layout(0, topView.getMeasuredHeight() + mMarginTop, mViewWidth, topView.getMeasuredHeight() + mMarginTop + bottomView.getMeasuredHeight());
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mViewHeight = getMeasuredHeight();
        mViewWidth = getMeasuredWidth();
    }


    class MyTimer {
        private Handler handler;
        private Timer timer;
        private MyTask mTask;

        public MyTimer(Handler handler) {
            this.handler = handler;
            timer = new Timer();
        }

        public void schedule(long period) {
            if (mTask != null) {
                mTask.cancel();
                mTask = null;
            }
            mTask = new MyTask(handler);
            timer.schedule(mTask, 0, period);
        }

        public void cancel() {
            if (mTask != null) {
                mTask.cancel();
                mTask = null;
            }
        }

        class MyTask extends TimerTask {
            private Handler handler;

            public MyTask(Handler handler) {
                this.handler = handler;
            }

            @Override
            public void run() {
                handler.obtainMessage().sendToTarget();
            }

        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (vt != null) {
            vt.recycle();
        }
    }
}
public class MyWebView extends WebView {


    public MyWebView(Context context) {
        this(context, null);
    }

    public MyWebView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public int getVerticalScrollOffset() {
        return this.computeVerticalScrollOffset();
    }
}

继续拖动,查看图文详情是WebView





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值