Android滑动冲突解决方法(二)

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

这里先看一张效果图

查看图文详情

由于GIF 图片大小的限制,截图效果不是很好

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

整体思路

这里看下图

多个ScrollView嵌套示意图

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

  • 黑色的框代表手机屏幕
  • 绿色的框代表一个外层的ScrollView,这个应该是实际应用中可见view的总长度
  • 两个红色的框代表嵌套在里面的两个类ScrollView控件,这里我们就暂时简称为 SUp,SDown

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

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

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

同理,这时候不允许外部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测试,实际中应考虑通过fragment切换实现。

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

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

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

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

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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容