• 关于Android滑动冲突的解决方法(二)


    之前的一遍学习笔记主要就Android滑动冲突中,在不同方向的滑动所造成冲突进行了了解,这样的冲突非常easy理解,当然也非常easy解决。今天,就同方向的滑动所造成的冲突进行一下了解,这里就先以垂直方向的滑动冲突为背景,这也是日常开发中最常见的一种情况。

    这里先看一张效果图

    查看图文详情

    由于GIF 图片大小的限制。截图效果不是非常好

    上图是在购物软件上常见的上拉查看图文详情,关于这中动画效果的实现。事实上实现总体的效果,办法是有非常多的,网上有非常多相关的样例,可是对某些细节的处理不是非常清晰。比方,下拉之后显示的部分(比如底部的图文详情)又是一个类ScrollView的控件(比方WebView)的话,又会产生新的问题。这里就下面拉查看图文详情为背景做一下同方向滑动冲突的分析。

    总体思路

    这里看下图

    多个ScrollView嵌套示意图

    首先。关于这张图做一些设定:

    • 黑色的框代表手机屏幕
    • 绿色的框代表一个外层的ScrollView
    • 两个红色的框代表嵌套在里面的两个类ScrollView控件1,这里我们就临时简称为 SUp,SDown

    好了,接下来就分析一下实现整个流程的过程。

    这里必须明白的一点。不管何时。SUp和SDown可见的部分始终是手机屏幕的高度。知道了这一点,我们就能够按下面步骤展开

    首先,我们确保外部的ScrollView不拦截滑动事件,这样SUp必定获得此次事件,然后依据其Action_Move事件,当其为向下滑动自身滑动距离+屏幕高度=其自身高度 时。就可以觉得SUp滑动到了底部,此时外部ScrollView可拦截滑动事件,从而保证能够继续向下滑动,这个时候底部SDown就显示出来了。

    同理,这时候外部ScrollView不同意外部ScrollView拦截滑动事件。由SDown处理。依据其Action_move事件,当其为向上滑动,且自身可滑动距离为0时。就说明SDown已经滑动到了顶部,这时外部ScrollView又能够获得拦截滑动事件的权利,从而保证整个视图能够向上继续滑动。此时SUp再次显示,有開始新一轮循环拦截。

    这样总体的一个流程就能够实现动图中的效果。

    好了。说完原理。还是看代码。

    代码实现

    SUp实现

    public class UpScrollView extends ScrollView {
        /**
         * 屏幕高度
         */
        private int mScreenHeight;
        /**
         * 上一次的坐标
         */
        private float mLastY;
        /**
         * 当前View滑动距离
         */
        private int mScrollY;
        /**
         * 当前View内子控件高度
         */
        private int mChildH;
    
    
        public UpScrollView(Context context) {
            super(context);
            init(context);
        }
    
        public UpScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
    
        public UpScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
    
        }
    
        private void init(Context context) {
            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            DisplayMetrics dm = new DisplayMetrics();
            wm.getDefaultDisplay().getMetrics(dm);
            mScreenHeight = dm.heightPixels;
        }
    
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            //默认设定顶层View不拦截
    
            getParent().getParent().requestDisallowInterceptTouchEvent(true);
    
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mLastY = (int) ev.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float y = ev.getY();
                    float deltaY = y - mLastY;
    
                    mChildH = this.getChildAt(0).getMeasuredHeight();
    
                    int translateY = mChildH - mScrollY;
    
                    //向上滑动时。假设translateY等于屏幕高度时,即表明滑动究竟部。可又顶层View控制滑动
                    if (deltaY < 0 && translateY == mScreenHeight) {
                        getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    }
                    break;
                default:
                    break;
    
    
            }
    
            return super.onTouchEvent(ev);
        }
    
        @Override
        protected void onScrollChanged(int l, int t, int oldl, int oldt) {
            super.onScrollChanged(l, t, oldl, oldt);
            mScrollY = t;
        }
    }

    这里在ACTION_MOVE里做了减法,事实上道理是一样的。

    onScrollChanged 是在View类中实现。查看其API能够看到其第二个參数t解释

    • @param t Current vertical scroll origin.

    即为当前View此次滑动的距离

    SDown实现

    public class MyWebView extends WebView {
        public float oldY;
        private int t;
    
        public MyWebView(Context context) {
            super(context);
            init();
        }
    
        public MyWebView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            WebSettings settings = getSettings();
            settings.setJavaScriptEnabled(true);
            setWebViewClient(new WebViewClient() {
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
                    view.loadUrl(url);
                    return true;
                }
            });
        }
    
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            getParent().getParent().requestDisallowInterceptTouchEvent(true);
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    oldY = ev.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
    
                    float Y = ev.getY();
                    float Ys = Y - oldY;
                    if (Ys > 0 && t == 0) {
                        getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    }
                    break;
    
            }
    
            return super.onTouchEvent(ev);
        }
    
        @Override
        protected void onScrollChanged(int l, int t, int oldl, int oldt) {
            this.t = t;
            super.onScrollChanged(l, t, oldl, oldt);
        }
    
    
    }

    看以看到,这里底部的View并没有继承ScrollView。而是选择继承了WebView。这里仅仅是为了方便。当然继承ScrollView也是没有问题。这里仅仅是须要按实际情况考虑。由于底部图文详情的内容就是一个WebView载入数据。

    这个类的实现,依照之前说的原理应该非常好理解。

    外部ScrollView

    public class CustomerScrollViews extends ScrollView {
        /**
         * 屏幕高度
         */
        private int mScreenHeight;
    
        private UpScrollView upScrollView;
        private MyWebView myWebView;
        private boolean init = false;
    
        private float fator = 0.2f;
        private int factorHeight;
    
        private boolean upShow = true;
    
    
        public CustomerScrollViews(Context context) {
            super(context);
            init(context);
        }
    
        public CustomerScrollViews(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
    
        public CustomerScrollViews(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
    
        }
    
        private void init(Context context) {
            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            DisplayMetrics dm = new DisplayMetrics();
            wm.getDefaultDisplay().getMetrics(dm);
            mScreenHeight = dm.heightPixels;
            factorHeight = (int) (mScreenHeight * fator);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (!init) {
    
                LinearLayout parentView = (LinearLayout) getChildAt(0);
                //获得内部的两个子view
                upScrollView = (UpScrollView) parentView.getChildAt(0);
                myWebView = (MyWebView) parentView.getChildAt(2);
    //            //并设定其高度为屏幕高度
                upScrollView.getLayoutParams().height = mScreenHeight;
                myWebView.getLayoutParams().height = mScreenHeight;
                init = true;
            }
    
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            super.onLayout(changed, l, t, r, b);
            if (changed) {
                scrollTo(0, 0);
            }
        }
    
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_UP:
                    int scrollY = getScrollY();
                    if (upShow) {
                        if (scrollY <= factorHeight) {
                            smoothScrollTo(0, 0);
                        } else {
                            smoothScrollTo(0, mScreenHeight);
                            upShow = false;
    
                        }
                    } else {
                        int scrollpadding = mScreenHeight - scrollY;
                        if (scrollpadding >= factorHeight) {
                            this.smoothScrollTo(0, 0);
                            upShow = true;
    
                        } else {
                            this.smoothScrollTo(0, mScreenHeight);
                        }
    
                    }
    
    
                    return true;
    
            }
            return super.onTouchEvent(ev);
        }
    
    
    }

    这个类的实现。就非常灵活了。在onMeasure方法中初始化完内部的View之后,在OnTouch方法中就能够依据实际需求完毕不同的逻辑实现,这里仅仅是为了仿照查看图文详情的效果。对整个视图通过ScrollView的smoothScrollTo方法进行位移变化。这个逻辑非常简单。

    这里重点说一下一个地方:

    upScrollView = (UpScrollView) parentView.getChildAt(0);
    myWebView = (MyWebView) parentView.getChildAt(2);

    你可能会奇怪中间的child(1)去了哪里?这里还要从MainActivity的布局文件说起

    dual_scrollview_activity_layout1.xml

    <?

    xml version="1.0" encoding="utf-8"?

    > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.example.dreamwork.activity.superscrollview.CustomerScrollViews android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <com.example.dreamwork.activity.superscrollview.UpScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@drawable/taobao" /> <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@drawable/taobao" /> <TextView android:textSize="20sp" android:padding="10dp" android:gravity="center" android:layout_marginTop="20dp" android:layout_marginBottom="60dp" android:text="查看图文详情" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </com.example.dreamwork.activity.superscrollview.UpScrollView> <include layout="@layout/selector_tab_items" /> <com.example.dreamwork.activity.superscrollview.MyWebView android:id="@+id/web" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> </com.example.dreamwork.activity.superscrollview.CustomerScrollViews> </LinearLayout>

    整个布局文件能够看出,我们在CustomerScrollViews这个最外层的自己定义ScrollView内部又放置了两个自己定义的ScrollView(就如我们看到的原理图那样)。仅仅只是在这两个ScrollView类控件的中间通过layout又放置一个LinearLayout,里面的内容就是在动图中看到的那个中间的写着qq,baidu字样的用于切换WebView内容的一个View。这里就不贴代码了。

    这样,你就能够理解之前的child(1)为什么被跳过了吧。

    使用

    public class DualScrollViewActivity1 extends Activity implements View.OnClickListener {
    
        private MyWebView webView;
    
        private TextView sinaTv, qqTv, baiduTv;
        private View line1, line2, line3;
    
    
        private final String BAIDU = "http://www.baidu.com";
        private final String QQ = "http://www.qq.com";
        private final String SINA = "http://sina.cn";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            InitView();
            sinaTv.performClick();
        }
    
        private void InitView() {
            setContentView(R.layout.dual_scrollview_activity_layout1);
            webView = V.f(this, R.id.web);
    
            sinaTv = V.f(this, R.id.one);
            sinaTv.setOnClickListener(this);
            qqTv = V.f(this, R.id.two);
            qqTv.setOnClickListener(this);
            baiduTv = V.f(this, R.id.three);
            baiduTv.setOnClickListener(this);
    
    
            line1 = V.f(this, R.id.line1);
            line2 = V.f(this, R.id.line2);
            line3 = V.f(this, R.id.line3);
        }
    
    
        @Override
        public void onClick(View v) {
            reset();
            String url = "";
            switch (v.getId()) {
                case R.id.one:
                    line1.setVisibility(View.VISIBLE);
                    url = SINA;
                    break;
                case R.id.two:
                    line2.setVisibility(View.VISIBLE);
                    url = QQ;
                    break;
                case R.id.three:
                    line3.setVisibility(View.VISIBLE);
                    url = BAIDU;
                    break;
                default:
                    break;
            }
    
    
            webView.loadUrl(url);
        }
    
        private void reset(){
            line1.setVisibility(View.GONE);
            line2.setVisibility(View.GONE);
            line3.setVisibility(View.GONE);
        }
    }

    这里关于底部View内容更新,WebView 通过载入不同URL实现不同视图效果。仅仅是作为Demo測试。

    这里对滑动冲突的解决方法。是由内而外的展开,默认使顶层View失去拦截能力。在由底部View的滑动距离,做出不同逻辑推断控制了顶层的拦截与否;这也是比較easy理解和实现的思路。当然。对于此类滑动冲突,有非常多不同思路,这里仅仅是列举其一。

    实际开发开发中,这样的带有同方向滑动特性的控件嵌套时,产生的问题不仅仅是滑动冲突。有时候会有内容显示不全或不显示的情况。

    最常见如ScrollView嵌套ListView。这样的情况仅仅需自己定义ListView使其高度计算为一个非常大的值,某种意义上让其失去了滑动的特性,可是这样也让ListView貌似失去了视图回收机制。这样的时候假设载入非常多非常多非常多图片,效果就会非常不理想。对于这样的情况,通过对ListView加入headView及footView也是一种解决的办法。但也得实际UI情况同意。

    ScrollView嵌套RecyclerView时略微麻烦一点。须要自己定义ScrollView,还须要自己定义实现LinearLayoutManager。

    由此可见。在Android中这样的带有相似特性的控件嵌套后,问题还是非常多的。对此,Google出了一个叫做NestedScrolling的东西。接下来准备研究一下,看看其在解决滑动冲突方面的优势。



    1. 这里说类ScrollView控件。就是说相似于ScrollView特性的控件。即自带滚动特性的控件。
  • 相关阅读:
    eWebEditor在ie9下按钮功能失效的解决办法
    FLV视频播放代码
    笔记 PHP常用 语句
    jquery 无刷新加载执行,显示数据
    常用的PHP与SQL语句
    PHP常用语句
    Ajax+php 无刷新更新数据.并将数据库操作改写成类.
    js下拉框联动代码
    PHP 更新功能 笔记
    MyEclipse8.5开发环境配置中SVN插件安装重点解析
  • 原文地址:https://www.cnblogs.com/slgkaifa/p/7183759.html
Copyright © 2020-2023  润新知