您的位置:首页 > 其它

关于ScrollView中嵌套ListView和Viewpager问题

2015-05-27 10:58 267 查看
转载:http://blog.csdn.net/hellovytas/article/details/13018205



Android默认是不支持ScrollView里嵌套ListView的(Android懒就懒吧,还找那么多借口~还有脸用“never”这种词!?)。

但是在开发中,ScrollView嵌套ListView的情况还是很多的,谁让那些大艺术家(交互设计师)一意孤行非要这么干呢。在网上查了很多资料,几乎把百度谷歌翻了个底朝天,用一种方法解决了一种问题,但却衍生出更多问题。就这样拆东墙补西墙,补到最后,终于看似完美了。

为了感谢网上那些乐于分享代码经验的程序猿们,我也在此把自己的解决方案分享给后来者,让大家少走些弯路。



我们公司产品的UI设计样式 异常丧心病狂,已经不是ScrollView嵌套一个ListView那么小的问题了。

我把所有的问题列一下:

1.解决ScrollView嵌套ListView时,ListView高度显示异常。

2.解决ScrollView嵌套ListView,ListView的拉到底部加载更多的解决方案。解决ScrollView自动调至底部的问题。

3.解决ScrollView嵌套ViewPager时的滑动冲突。

1.解决ScrollView嵌套ListView时,ListView高度显示异常。

ScrollView嵌套ListView后,由于冲突,ListView会只显示一两行的高度。尝试过各种解决方法,最后觉得计算ListView每项的高度,然后给它设置高度是最好的解决方案。下面这个方法是国外论坛上的大神写的,但是使用后会发现一些问题。



如果ListView的getView方法很复杂,并且LISTVIEW的数据很多,那么这个方法中的For循环会造成相当长时间的UI线程拥堵。这个问题好解决,把方法中的耗时操作放在线程中就OK了。因为setLayoutParams(params)是UI操作,所以放在线程外。下面是我修改过的使用方法:

[java]
view plaincopy





new Thread(new Runnable() {
public void run() {
setListViewHeightBasedOnChildren(mAdapter,mListView);
mHandler.sendEmptyMessage(UI_SHOW_LIST);
}
}).start();
//待setListViewHeightBasedOnChildren计算好高度后,mHandler里的UI_SHOW_LIST要做的就是下面这句。
mListView.setLayoutParams(params);

[java]
view plaincopy





private ViewGroup.LayoutParams params;
public void setListViewHeightBasedOnChildren(MyAdapter mAdapter,ListView listView) {
if (mAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0; i < mAdapter.getCount(); i++) {
View listItem = mAdapter.getView(i, null, listView);
int desiredWidth = MeasureSpec.makeMeasureSpec(mListView.getWidth(), MeasureSpec.AT_MOST);
listItem.measure(desiredWidth, 0);
totalHeight += listItem.getMeasuredHeight();
}

params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (mAdapter.getCount() - 1));
}

最后,在布局文件中,给ScrollView添加 android:fillViewport="true" 这个属性。

2.解决ScrollView嵌套ListView,ListView的拉至底部加载更多的解决方案。

解决完listview的高度问题,那么就得面对一个新的问题,在冲突面前,ListView已经无法用以前的方式来响应加载更多了。
其实加载更多很简单,只用在listview的adapter里,给数据源增加相应的数据,然后重新调用setListViewHeightBasedOnChildren方法即可。
问题就在于如果知道ListView已经滑动到底部了。我的方案是监听ScrollView的onScrollChanged方法,因为onScrollChanged是protected的,所以只能去重写ScrollView。

重写的ScrollView如下:

[java]
view plaincopy





public class MyScrollView extends ScrollView {
private ScrollChangedListener mScrollChangedListener;

public interface ScrollChangedListener
{
void onScrollChanged(int y);
}

public void setScrollChangedListener(ScrollChangedListener l)
{
mScrollChangedListener = l;
}

public MyScrollView(Context context) {
super(context, null);
setFadingEdgeLength(0);
}

public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
setFadingEdgeLength(0);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}

@Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if(mScrollChangedListener != null)
mScrollChangedListener.onScrollChanged(y);
}

}

在Activity中进行处理:

[java]
view plaincopy





boolean mEnableFlag=true;

[java]
view plaincopy





private int SCROLL_Y=0;
scrollView = (MyScrollView)findViewById(R.id.scroll_view);
scrollView.setScrollChangedListener(mScrollChangedListener);

private ScrollChangedListener mScrollChangedListener = new ScrollChangedListener() {
@Override
public void onScrollChanged(int y) {
int height=scrollView.getHeight();
int scrollViewMeasuredHeight=scrollView.getChildAt(0).getMeasuredHeight();
//System.out.println(">>>>>>>>>>>> "+"scrollY="+y+",height="+height+",scrollViewMeasuredHeight="+scrollViewMeasuredHeight);
SCROLL_Y=y;
if((y+height)>=scrollViewMeasuredHeight) {
if(mEnableFlag)
{
mEnableFlag=false;
// GO TO Load More!!!
}
}
}
};

我的思路是当ScrollView滑动到底部时,调用加载更多的方法。
为了防止上一次加载更多未完成时,重复触发,所以加了个mEnableFalg标记进行控制,大家在加载更多完成后,记得打开标记。
这样的解决方案会遇到一个问题,onResume时,由于ListView的高度已经算给它,所以它会去进行它的getView,这就会导致页面自动跳转到最底部。
为了解决这个问题,我加了SCROLL_Y变量用以记录当前ScrollView的Y轴坐标。

然后在onResume方法中添加如下代码:

[java]
view plaincopy





@Override
protected void onResume() {
super.onResume();
scrollView.smoothScrollTo(0, SCROLL_Y);
}

当然还有一种最简单的解决方法,mListView.setFocusable(false);。

3.解决ScrollView嵌套ViewPager时的滑动冲突。

[java]
view plaincopy





mViewPager.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
scrollView.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
scrollView.requestDisallowInterceptTouchEvent(false);
break;
}
return false;
}
});

这种解决方法虽然解决了两者的冲突,但是的缺点在于,在Viewpager里上下滑动无法让ScrollView响应到。虽然我个人觉得没啥必要,但大家非要解决的话,可以对ACTIOM_MOVE进行判断,可以对XY轴的移动坐标进行比较,如果Y轴移动量是X轴的2倍及以上,则算作上下滑动,则让ScrollView去滑去。

转载:http://blog.csdn.net/liudemingbingyu/article/details/8944439

2013-05-18 17:37
2268人阅读 评论(3)
收藏
举报

[java]
view plaincopy

google官方是不提倡在UI中嵌套可滚动控件,如ScrollView,ViewPager等,但有时候为了实现一定的效果也不得不用,其实很多知名App都是这样弄的。下面是我总结的两种嵌套情形。
一.当ScrollView中嵌套ViewPager的情形

这种比较常见,一般是在界面的最顶部放置一个ViewPager,用来展示图片还有一些文字说明,下面则是List,像之前版本的网易新闻客户端就是这样的效果。List滚动的同时ViewPager也会跟着滚动,这时候就要用到ScrollView中嵌套ViewPager。

由于ScrollView是垂直滚动的,ViewPager是水平滑动的。当在ViewPager上滑动的时候,如果在水平方向上的偏移量较垂直方向上的偏移量不是太大的时候,会有明显的ScrollView滚动问题,很影响用户体验。解决的方法我总结了两种:

1.用自定义ScrollView,通过覆写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent等方法,来人为的控制TouchEvent。于是在网上搜了下,从点击打开链接找到了一个方法。覆写ScrollView的onInterceptTouchEvent方法,通过对水平和垂直方向上的偏移量进行计算,让ScrollView决定是拦截TouchEvent还是传递给子View。试了下,效果挺好的,代码如下:

[java]
view plaincopy

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
xDistance = yDistance = 0f;
xLast = ev.getX();
yLast = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
final float curX = ev.getX();
final float curY = ev.getY();

xDistance += Math.abs(curX - xLast);
yDistance += Math.abs(curY - yLast);
xLast = curX;
yLast = curY;

if(xDistance > yDistance){
return false;
}
}

return super.onInterceptTouchEvent(ev);
}

2.自定义ViewPager,覆写dispatchTouchEvent方法。该方法来自:点击打开链接代码如下:

[java]
view plaincopy

boolean ret = super.dispatchTouchEvent(ev);
if(ret)
{
requestDisallowInterceptTouchEvent(true);
}
return ret;

当ret为true时,就设置不要父控件及祖先控件不允许拦截TouchEvent。该请求会被传递给该ViewPager的所有父控件。并且会持续从ACTION_DOWN到ACTION_UP整个过程。我感觉这种方法也简单些,效果也很好。

二.ViewPager中嵌套ScrollView,ScrollView中嵌套ViewPager
这种情况下,在ScrollView中嵌套的子ViewPager上滑动的时候该ViewPager并不会切换,而是切换父ViewPager。这时候,只能自定义ViewPager了,方法跟情形一中的第二种方法是一样的,效果也挺好的。使该子ViewPager获得TouchEvent而不传递给父及祖先控件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐