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操作,所以放在线程外。下面是我修改过的使用方法:
new Thread(new Runnable() {
public void run() {
setListViewHeightBasedOnChildren(mAdapter,mListView);
mHandler.sendEmptyMessage(UI_SHOW_LIST);
}
}).start();
//待setListViewHeightBasedOnChildren计算好高度后,mHandler里的UI_SHOW_LIST要做的就是下面这句。
mListView.setLayoutParams(params);
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" 这个属性。
解决完listview的高度问题,那么就得面对一个新的问题,在冲突面前,ListView已经无法用以前的方式来响应加载更多了。
其实加载更多很简单,只用在listview的adapter里,给数据源增加相应的数据,然后重新调用setListViewHeightBasedOnChildren方法即可。
问题就在于如果知道ListView已经滑动到底部了。我的方案是监听ScrollView的onScrollChanged方法,因为onScrollChanged是protected的,所以只能去重写ScrollView。
重写的ScrollView如下:
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);
}
}
boolean mEnableFlag=true;
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方法中添加如下代码:
@Override
protected void onResume() {
super.onResume();
scrollView.smoothScrollTo(0, SCROLL_Y);
}
当然还有一种最简单的解决方法,mListView.setFocusable(false);。
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去滑去。
————————————————
转载时请注明出处!谢谢合作!
我将随时更新更好的解决内容,以方便更多猿类。