在介绍解决冲突关键点前,先介绍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中只显示一行的问题