您的位置:首页 > 其它

让listview在scrollview中自由滑动

2016-04-07 12:57 169 查看
总有人我listview嵌套scrollview怎么弄。一问就是半天,太耗时,所以写个博客也算是自己总结一下。

目标

scrollview嵌套listview,可以自由的定义listview的大小,而不是展示全部listview。

让listview在scrollview中自由滑动。

当listview滑动到顶端或底端的时候,让scrollview开始滑动

直接上图看效果好了:



代码也很简单 直接上代码

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="wang.com.scrollholdlistview.MainActivity">

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

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_vertical_margin"
android:text="Hello World!"
android:textSize="30sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_vertical_margin"
android:text="Hello World!"
android:textSize="30sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_vertical_margin"
android:text="Hello World!"
android:textSize="30sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_vertical_margin"
android:text="Hello World!"
android:textSize="30sp" />

<wang.com.scrollholdlistview.MyListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="@color/colorAccent"></wang.com.scrollholdlistview.MyListView>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_vertical_margin"
android:text="Hello World!"
android:textSize="30sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_vertical_margin"
android:text="Hello World!"
android:textSize="30sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_vertical_margin"
android:text="Hello World!"
android:textSize="30sp" />

<wang.com.scrollholdlistview.MyListView
android:id="@+id/lv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent">

</wang.com.scrollholdlistview.MyListView>
</LinearLayout>
</ScrollView>


package wang.com.scrollholdlistview;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ListView;

/**
* 创建日期: 16/4/3 下午9:55
* 作者:wanghao
* 描述:
*/
public class MyListView extends ListView {

private static final String TAG = "MyListView";

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

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

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

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int defaultsize=measureHight(Integer.MAX_VALUE >> 2, heightMeasureSpec);
int expandSpec = MeasureSpec.makeMeasureSpec(defaultsize, MeasureSpec.AT_MOST);

super.onMeasure(widthMeasureSpec, expandSpec);
}

private int measureHight(int size, int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
Log.i(TAG, "exactly" );

result = specSize;
} else {

result = size;//最小值是200px ,自己设定
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
Log.i(TAG, "specMode:"+specMode+"--result:"+result );
}
return result;
}

/**
* 改listview滑到底端了
*
* @return
*/
public boolean isBottom() {
int firstVisibleItem = getFirstVisiblePosition();//屏幕上显示的第一条是list中的第几条
int childcount = getChildCount();//屏幕上显示多少条item
int totalItemCount = getCount();//一共有多少条
if ((firstVisibleItem + childcount) >=totalItemCount) {
return true;
}
return false;
}

/**
* 改listview在顶端
*
* @return
*/
public boolean isTop() {
int firstVisibleItem = getFirstVisiblePosition();
if (firstVisibleItem ==0) {
return true;
}
return false;
}

float down = 0;
float y;

@Override
public boolean dispatchTouchEvent(MotionEvent event) {

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
down = event.getRawY();

getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
y = event.getRawY();
if (isTop()) {
if (y - down > 1) {
//                        到顶端,向下滑动 把事件教给父类
getParent().requestDisallowInterceptTouchEvent(false);
} else {
//                        到顶端,向上滑动 把事件拦截 由自己处理
getParent().requestDisallowInterceptTouchEvent(true);
}
}

if (isBottom()) {
if (y - down > 1) {
//                        到底端,向下滑动 把事件拦截 由自己处理
getParent().requestDisallowInterceptTouchEvent(true);
} else {
//                        到底端,向上滑动 把事件教给父类
getParent().requestDisallowInterceptTouchEvent(false);
}
}
break;
default:
break;
}

return super.dispatchTouchEvent(event);
}
}


上面代码直接用就可以啦。

那么下面说下好多人遇到过的疑惑

为什么这么写就能避免嵌套问题呢

下面代码是网上流传最多的

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int expandSpec = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}


这样写呢有个弊端,就是 在scrollview里面展示了 listview的全部内容。有100条就显示了100条。

Integer.MAX_VALUE >> 2这个呢就是一个值。让你的listview最大值是多大。你可以写成65535,或者其他数值。为什么用 MeasureSpec.AT_MOST呢?

看下super里面

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

...
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}

setMeasuredDimension(widthSize, heightSize);

mWidthMeasureSpec = widthMeasureSpec;
}


再看下measureHeightOfChildren()的内容

final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
int maxHeight, int disallowPartialChildPosition) {
final ListAdapter adapter = mAdapter;
if (adapter == null) {
return mListPadding.top + mListPadding.bottom;
}

// Include the padding of the list
int returnedHeight = mListPadding.top + mListPadding.bottom;
final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
// The previous height value that was less than maxHeight and contained
// no partial children
int prevHeightWithoutPartialChild = 0;
int i;
View child;

// mItemCount - 1 since endPosition parameter is inclusive
endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;//获取有多少个item
final AbsListView.RecycleBin recycleBin = mRecycler;
final boolean recyle = recycleOnMeasure();
final boolean[] isScrap = mIsScrap;
//遍历所有item并且 获取到 所有item的整体高度
for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i, isScrap);

measureScrapChild(child, i, widthMeasureSpec, maxHeight);

if (i > 0) {
// Count the divider for all but one child
returnedHeight += dividerHeight;
}

// Recycle the view before we possibly return from the method
if (recyle && recycleBin.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
recycleBin.addScrapView(child, -1);
}

returnedHeight += child.getMeasuredHeight();
//如果item的高度大于咱们赋值的高度也就是Integer.MAX_VALUE >> 2
if (returnedHeight >= maxHeight) {
// We went over, figure out which height to return.  If returnedHeight > maxHeight,
// then the i'th position did not fit completely.
return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
&& (i > disallowPartialChildPosition) // We've past the min pos
&& (prevHeightWithoutPartialChild > 0) // We have a prev height
&& (returnedHeight != maxHeight) // i'th child did not fit completely
? prevHeightWithoutPartialChild 如果是大于就用自己item整体的高度作为listview的高度
: maxHeight; 如果是等于那么久用 我们赋值的高度
}
//如果item的高度 不大于等于 默认值 那么久显示他自己的高度了
if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
prevHeightWithoutPartialChild = returnedHeight;
}
}

// At this point, we went through the range of children, and they each
// completely fit, so return the returnedHeight
return returnedHeight;
}


所以我们赋值的Integer.MAX_VALUE >> 2只是一个参考值,你想写多少就是多少。

但是那样写有个弊端就是在xml里面写的高度 就没用了。所以我们就这么写了

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int defaultsize=measureHight(Integer.MAX_VALUE >> 2, heightMeasureSpec);
int expandSpec = MeasureSpec.makeMeasureSpec(defaultsize, MeasureSpec.AT_MOST);

super.onMeasure(widthMeasureSpec, expandSpec);
}

private int measureHight(int size, int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
Log.i(TAG, "exactly" );

result = specSize;
} else {

result = size;//最小值是200px ,自己设定
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
Log.i(TAG, "specMode:"+specMode+"--result:"+result );
}
return result;
}


这样的话就是如果 xml里面是wrap_content的话 就显示所有item的高度。如果是设置了 高度值 比如是400dp。那么listview显示的高度就是400dp。

为什么那么些 请戳这尼自定义view,viewgroup的onMeasure 方法

那么问题又来了。

如果显示400dp的高度。listview就没法全部显示了。

所以就不能让scrollview拦截事件,要让该事件传递到listview,并且处理。 一行代码就搞定

getParent().requestDisallowInterceptTouchEvent(true);
true:就是拦截此事件
false:不拦截此事件,让父类处理


剩下的就只是当listview滑动到顶端和底端 listview就拦截该事件了。代码里已经体现的很简单了。

尊重原创:http://blog.csdn.net/wanghao200906/article/details/51084975

代码在这里

最近太忙了。发个福利安慰一下自己

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: