转载: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操作,所以放在线程外。下面是我修改过的使用方法:
- 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” 这个属性。
2.解决ScrollView嵌套ListView,ListView的拉至底部加载更多的解决方案。
解决完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);
- }
- }
在Activity中进行处理:
- 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!!!
- }
- }
- }
- };
为了防止上一次加载更多未完成时,重复触发,所以加了个mEnableFalg标记进行控制,大家在加载更多完成后,记得打开标记。
这样的解决方案会遇到一个问题,onResume时,由于ListView的高度已经算给它,所以它会去进行它的getView,这就会导致页面自动跳转到最底部。
为了解决这个问题,我加了SCROLL_Y变量用以记录当前ScrollView的Y轴坐标。
然后在onResume方法中添加如下代码:
当然还有一种最简单的解决方法,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;
- }
- });
转载:http://blog.csdn.Net/liudemingbingyu/article/details/8944439
2013-05-18 17:37 2268人阅读 评论(3) 收藏 举报
一.当ScrollView中嵌套ViewPager的情形
这种比较常见,一般是在界面的最顶部放置一个ViewPager,用来展示图片还有一些文字说明,下面则是List,像之前版本的网易新闻客户端就是这样的效果。List滚动的同时ViewPager也会跟着滚动,这时候就要用到ScrollView中嵌套ViewPager。
由于ScrollView是垂直滚动的,ViewPager是水平滑动的。当在ViewPager上滑动的时候,如果在水平方向上的偏移量较垂直方向上的偏移量不是太大的时候,会有明显的ScrollView滚动问题,很影响用户体验。解决的方法我总结了两种:
1.用自定义ScrollView,通过覆写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent等方法,来人为的控制TouchEvent。于是在网上搜了下,从点击打开链接找到了一个方法。覆写ScrollView的onInterceptTouchEvent方法,通过对水平和垂直方向上的偏移量进行计算,让ScrollView决定是拦截TouchEvent还是传递给子View。试了下,效果挺好的,代码如下:
- @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);
- }
- boolean ret = super.dispatchTouchEvent(ev);
- if(ret)
- {
- requestDisallowInterceptTouchEvent(true);
- }
- return ret;
二.ViewPager中嵌套ScrollView,ScrollView中嵌套ViewPager
这种情况下,在ScrollView中嵌套的子ViewPager上滑动的时候该ViewPager并不会切换,而是切换父ViewPager。这时候,只能自定义ViewPager了,方法跟情形一中的第二种方法是一样的,效果也挺好的。使该子ViewPager获得TouchEvent而不传递给父及祖先控件。