解决ListView与ScrollView的滑动冲突(非自定义View方案)

在介绍解决冲突关键点前,先介绍AbsListView.OnScrollListener,该类用于监听AbsListView类及其子类的滑动。

(注意:点击Item并不会引起任何该监听器的回调,因为没有滑动状态改变或滑动)

先介绍AbsListView.OnScrollListener#onScrollStateChanged(AbsListView view, int scrollState):在滑动状态改变的时候被回调。

状态如下所示:

1.停止滑动了,即手指在上面没有滑动了,且列表也没有滑动了

2.手指在触摸且开始动了

3.手指离开,列表在动

注:如果列表到底部或顶部了,手指依旧持续滑动不停止,那么不会产生滑动状态改变(即SCROLL_STATE_IDLE不包括这种情况)。SCROLL_STATE_FLING这个状态引起的回调只是一次,就是手指离开那一瞬间(若离开时列表在滑动)。而SCROLL_STATE_IDLE和SCROLL_STATE_TOUCH_SCROLL可以交替产生,若手指一直没离开,滑一下然后停下,再滑动。这个回调方法看来,只要手指在滑动方向上滑动,就叫滑动,无论列表有无滑动(而下一次待介绍的回调方法就不一样了)。还有,在该方法中取消该AbsListView的父类的disallowIntercept标志是无效的,因为接下来AbsListView会在某个地方将其父类的disallowIntercept标志设为true,但在onScroll回调中有效

/**
         * The view is not scrolling. Note navigating the list using the trackball counts as
         * being in the idle state since these transitions are not animated.
         */
        public static int SCROLL_STATE_IDLE = 0;

        /**
         * The user is scrolling using touch, and their finger is still on the screen
         */
        public static int SCROLL_STATE_TOUCH_SCROLL = 1;

        /**
         * The user had previously been scrolling using touch and had performed a fling. The
         * animation is now coasting to a stop
         */
        public static int SCROLL_STATE_FLING = 2;

AbsListView.OnScrollListener#onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount):这个方法在AbsListView及其子类在滑动时会调用,这个方法所指滑动是列表真正在滑动,而不是指手指滑动,所以在列表到达底部(顶部)滑不动时,即使手指在滑动也不会触发这个回调方法。这个方法在列表滑动过程中会不断被回调,直到滑动停止()。(注:无论手指有没有触摸,只要列表在滑动,就会不断回调该方法。)



下面开始介绍解决冲突的代码:

package com.example.zhangjinbiao.compatablescrollview;

import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ScrollView;

import com.google.android.gms.appindexing.Action;
import com.google.android.gms.appindexing.AppIndex;
import com.google.android.gms.appindexing.Thing;
import com.google.android.gms.common.api.GoogleApiClient;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements ViewTreeObserver.OnGlobalLayoutListener{

    private ListView mListView;
    private ScrollView mScrollView;
    private ArrayList<String> list = new ArrayList<String>();
    private View mContentView ;
    //private boolean ismListViewScrolling = false;
    private boolean ismListViewscrollUp = false;
    private VelocityTracker mVelocityTracker;
    private boolean ismListViewScrollToBottom = false;
    /**
     * ATTENTION: This was auto-generated to implement the App Indexing API.
     * See https://g.co/AppIndexing/AndroidStudio for more information.
     */
    private GoogleApiClient client;

    @RequiresApi(api = Build.VERSION_CODES.M)
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView) findViewById(R.id.lv_1);
        mScrollView = (ScrollView) findViewById(R.id.sv_1);

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1, getListData());
        mListView.setAdapter(adapter);
 
        client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build();
        mContentView = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0);
        
        int height = View.MeasureSpec.getSize(1073742782);
        int mode = View.MeasureSpec.getMode(1073742782);


     

        if (mListView != null) {
//为ListView设置一个监听器,为了控制事件的派发。在ACTION_DOWN派发给ListView时,为mScrollView设置DisallowIntercept标志,式mScrollView不能拦截该事件序列接
//下来的事件,直到mListView取消该标志。
 mListView.setOnTouchListener(new View.OnTouchListener() {

                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if(mScrollView == null){
                        return false;
                    }
                    
                    switch (event.getAction()){
                        case MotionEvent.ACTION_DOWN:
                            mScrollView.requestDisallowInterceptTouchEvent(true);
                            break;
                        case MotionEvent.ACTION_MOVE:
                            mVelocityTracker = VelocityTracker.obtain();
                            mVelocityTracker.addMovement(event);
                            mVelocityTracker.computeCurrentVelocity(500);
                            final float velocity =mVelocityTracker.getYVelocity();
                            if(velocity < 0){
                                ismListViewscrollUp = true;
                            }else{
                                ismListViewscrollUp = false;
                            }
                            if(ismListViewScrollToBottom && ismListViewscrollUp){
//当滑动ListView至其底部且手指还在往上滑动时,取消mScrollView的disallowIntercept标志,让mScrollView截取并处理序列中接下来的事件,并且返回true,表示消耗了
//该事件,不让ListView的onTouch()再来处理这个事件,以免onTouch()再次设置mScrollView的disallowIntercept标志。这也是为什么在
//AbsListView.OnScrollListener#onScrollStateChanged(AbsListView view, int scrollState)中取消mScrollView的disallowIntercept标志
//无效的原因
                                 mScrollView.requestDisallowInterceptTouchEvent(false);
              
                                return true;
                            }
                            break;
                        case MotionEvent.ACTION_UP:
                            mScrollView.requestDisallowInterceptTouchEvent(false);
                            break;
                        case  MotionEvent.ACTION_CANCEL:
                            ismListViewscrollUp = false;
                            break;
                        default:
                            break;
                    }
                    return false;
                }
            });
        }

        
        if (mListView != null) {
 //为mListView设置滑动监听器,并在此判断是否到达底部。为什么要在两个方法中都要判断呢?原因在上面关于该监听器的解析中已经有答案的了,在此我再次阐明一下。
//因为onScrollStateChanged方法在一直滑动的时候,没有发生状态改变,即使到达了底部,只要手指还在滑动,就不会触发该方法,在此时,就要用到onScroll方法来判断。
//而onScroll方法在手指为按下,而列表已经到达底部时,手指向上滑动,因为到达底部的ListView并没有真正滑动,所以不会触发onScroll,而此时会触发
//onScrollStateChanged,触发的scrollState为SCROLL_STATE_TOUCH_SCROLL,即有不滑动到滑动的改变(前面已知道该回调的滑动指手指在滑动,即使列表没有真正滑动)
                mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(AbsListView view, int scrollState) {
                   
                    if (view.getLastVisiblePosition() == (view.getCount() - 1) && ismListViewscrollUp) {
                 
                        ismListViewScrollToBottom = true;
                    }else{
                        ismListViewScrollToBottom = false;
                    }
                }

                @Override
                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                    Log.e("ListView","move");
                    if (view.getLastVisiblePosition() == (view.getCount() - 1) && ismListViewscrollUp) {
                        
                        ismListViewScrollToBottom = true;
                    }else {
                        ismListViewScrollToBottom = false;
                    }

                }
            });


        }
        /**
         * set ToucListener let listView to slide 14:49 22/11/2016 end
         */
    }

    public List<String> getListData() {
        list.add("Item 1");
        list.add("Item 2");
        list.add("Item 3");
        list.add("Item 4");
        list.add("Item 5");
        list.add("Item 5");
        list.add("Item 5");
        list.add("Item 5");
        list.add("Item 5");
        list.add("Item 5");
        return list;
    }

   

    /**
     * ATTENTION: This was auto-generated to implement the App Indexing API.
     * See https://g.co/AppIndexing/AndroidStudio for more information.
     */
    public Action getIndexApiAction() {
        Thing object = new Thing.Builder()
                .setName("Main Page") // TODO: Define a title for the content shown.
                // TODO: Make sure this auto-generated URL is correct.
                .setUrl(Uri.parse("http://[ENTER-YOUR-URL-HERE]"))
                .build();
        return new Action.Builder(Action.TYPE_VIEW)
                .setObject(object)
                .setActionStatus(Action.STATUS_TYPE_COMPLETED)
                .build();
    }

    @Override
    public void onStart() {
        super.onStart();

        // ATTENTION: This was auto-generated to implement the App Indexing API.
        // See https://g.co/AppIndexing/AndroidStudio for more information.
        client.connect();
        AppIndex.AppIndexApi.start(client, getIndexApiAction());
    }

    @Override
    public void onStop() {
        super.onStop();

        // ATTENTION: This was auto-generated to implement the App Indexing API.
        // See https://g.co/AppIndexing/AndroidStudio for more information.
        AppIndex.AppIndexApi.end(client, getIndexApiAction());
        client.disconnect();
    }

   
}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    android:orientation="vertical"
    tools:context="com.example.zhangjinbiao.compatablescrollview.MainActivity">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:id="@+id/sv_1">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
        <ListView
            android:id="@+id/lv_1"
            android:layout_width="400dp"
            android:layout_height="150dp"
            >

        </ListView>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000i love you scrollView" />
        </LinearLayout>
    </ScrollView>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

</LinearLayout>

总结一下:那就是为mListView设置监听器控制事件派发过程,判断手指滑动方向和是否让mScrollView截取并处理接下来的事件,为mListView设置OnScrollListener判断是否到达ListView底部。


注意:这里只是举例说明解决冲突的思路,该代码的功能并不完善,该代码示例解决的只是ListView没有到达底部手指向上滑则让ListView滑动而ScrollView不动,若到达ListView底部时,手指向上滑动时ScrollView会滑动ListView不动。但是,没有考虑ListView到达顶部手指向下滑时让ScrollView滑动(这个情况跟前面所述的情况是一样的思路的,可以根据前面的解决方法去解决)。因为该布局文件的界面是ListView的顶部即ScrollView的顶部。解决的关键是明白控件树事件派发的过程。


浅析ListView在ScrollView中只显示一行的问题







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值