本文主要是介绍ScrollView,ListView,GrideView,RecyclerView,ViewPager等多种view嵌套问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
在开发中各种滑动布局的view如ScrollView,ListView等非常常用,但是你也会发现各种奇怪问题产生。网上的解决方法有很多种,但是杂而不全,根据个人经验现在列出常见问题以及代码最少最简单的解决方法,首先你要了解事件的分发机制及事件冲突,这里不再赘述。(其他继承自AbsListView的类也适用,包括ExpandableListView、GridView等等)
-
ScrollView嵌套ListView冲突问题的最优解决方案
| ScrollView嵌套ListVew或者GridView等很常用,下面来说解决版本 问题一 : 嵌套在 ScrollView的 ListVew数据显示不全,我遇到的是最多只显示两条已有的数据。 解决办法:重写 ListVew或者 GridView,网上还有很多若干解决办法,但是都不好用或者很复杂。
问题二 、打开套有 ListVew的 ScrollView的页面布局 默认 起始位置不是最顶部。 解决办法有两种都挺好用: 一是把套在里面的Gridview 或者 ListVew 不让获取焦点即可。 gridview.setFocusable(false); listview.setFocusable(false); 注意:在xml布局里面设置android:focusable=“false”不生效 方法二:网上还查到说可以设置myScrollView.smoothScrollTo(0,0); |
-
看错误的布局代码:<ScrollViewandroid:id="@+id/act_solution_1_sv"android:layout_width="fill_parent"android:layout_height="fill_parent"><LinearLayoutandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="\nListView上方数据\n" /><ListViewandroid:id="@+id/act_solution_1_lv"android:layout_width="fill_parent"android:layout_height="wrap_content"></ListView><TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="\nListView下方数据\n" /></LinearLayout></ScrollView>ScrollView中只能放一个控件,一般都放LinearLayout,orientation属性值为vertical。在LinearLayout中放需要呈现的内容。ListView也在其中,ListView的高度设为适应自身内容(wrap_content原因就是scroll事件的消费处理以及ListView控件的高度设定问题
-
ViewPager和ScrollView嵌套滚动问题解决方案
-
嵌套是ViewPager-->ScrollView-->ViewPager.public class HorizontalInnerViewPager extends ViewPager {/** 触摸时按下的点 **/PointF downP = new PointF();/** 触摸时当前的点 **/PointF curP = new PointF();/** 自定义手势**/private GestureDetector mGestureDetector;public HorizontalInnerViewPager(Context context, AttributeSet attrs) {super(context, attrs);mGestureDetector = new GestureDetector(context, new XScrollDetector());}public HorizontalInnerViewPager(Context context) {super(context);mGestureDetector = new GestureDetector(context, new XScrollDetector());}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return super.onInterceptTouchEvent(ev);//default//当拦截触摸事件到达此位置的时候,返回true,//说明将onTouch拦截在此控件,进而执行此控件的onTouchEvent// return true;//接近水平滑动时子控件处理该事件,否则交给父控件处理// return mGestureDetector.onTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent ev) {//每次进行onTouch事件都记录当前的按下的坐标curP.x = ev.getX();curP.y = ev.getY();if(ev.getAction() == MotionEvent.ACTION_DOWN){//记录按下时候的坐标//切记不可用 downP = curP ,这样在改变curP的时候,downP也会改变downP.x = ev.getX();downP.y = ev.getY();//此句代码是为了通知他的父ViewPager现在进行的是本控件的操作,不要对我的操作进行干扰getParent().requestDisallowInterceptTouchEvent(true);}if(ev.getAction() == MotionEvent.ACTION_MOVE){float distanceX = curP.x - downP.x;float distanceY = curP.y - downP.y;//接近水平滑动,ViewPager控件捕获手势,水平滚动if(Math.abs(distanceX) > Math.abs(distanceY)){//此句代码是为了通知他的父ViewPager现在进行的是本控件的操作,不要对我的操作进行干扰getParent().requestDisallowInterceptTouchEvent(true);}else{//接近垂直滑动,交给父控件处理getParent().requestDisallowInterceptTouchEvent(false);}}return super.onTouchEvent(ev);}private class XScrollDetector extends GestureDetector.SimpleOnGestureListener{@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {// return super.onScroll(e1, e2, distanceX, distanceY);//接近水平滑动时子控件处理该事件,否则交给父控件处理return (Math.abs(distanceX) > Math.abs(distanceY));}}}
-
ScrollView里嵌套ScrollView
-
两个相同方向的ScrollView是不能嵌套的,所以尽量避免这种情况; 如果不能避免,则看下面目前做的这个只支持两个ScrollView嵌套,两个以上还有待改进,能套两个就已经能满足很多需求了目前为纵向scrollview的支持.直接看代码:public class InnerScrollView extends ScrollView {/***/public ScrollView parentScrollView;public InnerScrollView(Context context, AttributeSet attrs) {super(context, attrs);}private int lastScrollDelta = 0;public void resume() {overScrollBy(0, -lastScrollDelta, 0, getScrollY(), 0, getScrollRange(), 0, 0, true);lastScrollDelta = 0;}int mTop = 10;/*** 将targetView滚到最顶端*/public void scrollTo(View targetView) {int oldScrollY = getScrollY();int top = targetView.getTop() - mTop;int delatY = top - oldScrollY;lastScrollDelta = delatY;overScrollBy(0, delatY, 0, getScrollY(), 0, getScrollRange(), 0, 0, true);}private int getScrollRange() {int scrollRange = 0;if (getChildCount() > 0) {View child = getChildAt(0);scrollRange = Math.max(0, child.getHeight() - (getHeight()));}return scrollRange;}int currentY;@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {if (parentScrollView == null) {return super.onInterceptTouchEvent(ev);} else {if (ev.getAction() == MotionEvent.ACTION_DOWN) {// 将父scrollview的滚动事件拦截currentY = (int)ev.getY();setParentScrollAble(false);return super.onInterceptTouchEvent(ev);} else if (ev.getAction() == MotionEvent.ACTION_UP) {// 把滚动事件恢复给父ScrollviewsetParentScrollAble(true);} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {}}return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent ev) {View child = getChildAt(0);if (parentScrollView != null) {if (ev.getAction() == MotionEvent.ACTION_MOVE) {int height = child.getMeasuredHeight();height = height - getMeasuredHeight();// System.out.println("height=" + height);int scrollY = getScrollY();// System.out.println("scrollY" + scrollY);int y = (int)ev.getY();// 手指向下滑动if (currentY < y) {if (scrollY <= 0) {// 如果向下滑动到头,就把滚动交给父ScrollviewsetParentScrollAble(true);return false;} else {setParentScrollAble(false);}} else if (currentY > y) {if (scrollY >= height) {// 如果向上滑动到头,就把滚动交给父ScrollviewsetParentScrollAble(true);return false;} else {setParentScrollAble(false);}}currentY = y;}}return super.onTouchEvent(ev);}/*** 是否把滚动事件交给父scrollview** @param flag*/private void setParentScrollAble(boolean flag) {parentScrollView.requestDisallowInterceptTouchEvent(!flag);}}
-
RecyclerView嵌套滚动问题
在 Android 应用中,大部分情况下都会使用一个垂直滚动的 View 来显示内容(比如 ListView、RecyclerView 等)。但是有时候你还希望垂直滚动的View 里面的内容可以水平滚动。如果直接在垂直滚动的 View 里面使用水平滚动的 View,则滚动操作并不是很流畅。
比如下图中的示例:

为什么会出现这个问题呢?
上图中的布局为一个 RecyclerView 使用的是垂直滚动的 LinearLayoutManager 布局管理器,而里面每个 Item 为另外一个 RecyclerView 使用的是水平滚动的 LinearLayoutManager。而在 Android系统的事件分发 中,即使最上层的 View 只能垂直滚动,当用户水平拖动的时候,最上层的 View 依然会拦截点击事件。下面是 RecyclerView.java 中 onInterceptTouchEvent 的相关代码:
@Override public boolean onInterceptTouchEvent(MotionEvent e) { ...switch (action) {case MotionEvent.ACTION_DOWN:...case MotionEvent.ACTION_MOVE: {...if (mScrollState != SCROLL_STATE_DRAGGING) {boolean startScroll = false;if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {...startScroll = true;}if (canScrollVertically && Math.abs(dy) > mTouchSlop) {...startScroll = true;}if (startScroll) {setScrollState(SCROLL_STATE_DRAGGING);}}} break;...}return mScrollState == SCROLL_STATE_DRAGGING; }注意上面的 if 判断:
if(canScrollVertically && Math.abs(dy) > mTouchSlop) {...}RecyclerView 并没有判断用户拖动的角度, 只是用来判断拖动的距离是否大于滚动的最小尺寸。 如果是一个只能垂直滚动的 View,这样实现是没有问题的。如果我们在里面再放一个 水平滚动的 RecyclerView ,则就出现问题了。
可以通过如下的方式来修复该问题:
if(canScrollVertically && Math.abs(dy) > mTouchSlop && (canScrollHorizontally || Math.abs(dy) > Math.abs(dx))) {...}下面是一个完整的实现 BetterRecyclerView.java :
public class BetterRecyclerView extends RecyclerView{private static final int INVALID_POINTER = -1;private int mScrollPointerId = INVALID_POINTER;private int mInitialTouchX, mInitialTouchY;private int mTouchSlop;public BetterRecyclerView(Contextcontext) {this(context, null);}public BetterRecyclerView(Contextcontext, @Nullable AttributeSetattrs) {this(context, attrs, 0);}public BetterRecyclerView(Contextcontext, @Nullable AttributeSetattrs, int defStyle) {super(context, attrs, defStyle);final ViewConfigurationvc = ViewConfiguration.get(getContext());mTouchSlop = vc.getScaledTouchSlop();}@Overridepublic void setScrollingTouchSlop(int slopConstant) {super.setScrollingTouchSlop(slopConstant);final ViewConfigurationvc = ViewConfiguration.get(getContext());switch (slopConstant) {case TOUCH_SLOP_DEFAULT:mTouchSlop = vc.getScaledTouchSlop();break;case TOUCH_SLOP_PAGING:mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc);break;default:break;}}@Overridepublic boolean onInterceptTouchEvent(MotionEvent e) {final int action = MotionEventCompat.getActionMasked(e);final int actionIndex = MotionEventCompat.getActionIndex(e);switch (action) {case MotionEvent.ACTION_DOWN:mScrollPointerId = MotionEventCompat.getPointerId(e, 0);mInitialTouchX = (int) (e.getX() + 0.5f);mInitialTouchY = (int) (e.getY() + 0.5f);return super.onInterceptTouchEvent(e);case MotionEventCompat.ACTION_POINTER_DOWN:mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);mInitialTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);mInitialTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);return super.onInterceptTouchEvent(e);case MotionEvent.ACTION_MOVE: {final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);if (index < 0) {return false;}final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);if (getScrollState() != SCROLL_STATE_DRAGGING) {final int dx = x - mInitialTouchX;final int dy = y - mInitialTouchY;final boolean canScrollHorizontally = getLayoutManager().canScrollHorizontally();final boolean canScrollVertically = getLayoutManager().canScrollVertically();boolean startScroll = false;if (canScrollHorizontally && Math.abs(dx) > mTouchSlop && (Math.abs(dx) >= Math.abs(dy) || canScrollVertically)) {startScroll = true;}if (canScrollVertically && Math.abs(dy) > mTouchSlop && (Math.abs(dy) >= Math.abs(dx) || canScrollHorizontally)) {startScroll = true;}return startScroll && super.onInterceptTouchEvent(e);}return super.onInterceptTouchEvent(e);}default:return super.onInterceptTouchEvent(e);}} }其他问题
当用户快速滑动(fling)RecyclerView 的时候, RecyclerView 需要一段时间来确定其最终位置。 如果用户在快速滑动一个子的水平 RecyclerView,在子 RecyclerView 还在滑动的过程中,如果用户垂直滑动,则是无法垂直滑动的。原因是子 RecyclerView 依然处理了这个垂直滑动事件。

所以,在快速滑动后的滚动到静止的状态中,子 View 不应该响应滑动事件了,再次看看 RecyclerView 的 onInterceptTouchEvent() 代码:
@Override public boolean onInterceptTouchEvent(MotionEvent e) { ...switch (action) {case MotionEvent.ACTION_DOWN:...if (mScrollState == SCROLL_STATE_SETTLING) {getParent().requestDisallowInterceptTouchEvent(true);setScrollState(SCROLL_STATE_DRAGGING);}...}return mScrollState == SCROLL_STATE_DRAGGING; }可以看到,当 RecyclerView 的状态为 SCROLL_STATE_SETTLING (快速滑动后到滑动静止之间的状态)时, RecyclerView 告诉父控件不要拦截事件。
同样的,如果只有一个方向固定,这样处理是没问题的。
针对我们这个嵌套的情况,父 RecyclerView 应该只拦截垂直滚动事件,所以可以这么修改父 RecyclerView:
public class FeedRootRecyclerView extends BetterRecyclerView{ public FeedRootRecyclerView(Contextcontext) {this(context, null);}public FeedRootRecyclerView(Contextcontext, @Nullable AttributeSetattrs) {this(context, attrs, 0);}public FeedRootRecyclerView(Contextcontext, @Nullable AttributeSetattrs, int defStyle) {super(context, attrs, defStyle);}@Overridepublic void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {/* do nothing */} }下图为最终的结果:

-
-
-
recycleview跟scrollview嵌套问题
scrollview 嵌套recyclerview 时,recyclerview不显示,这就需要我们自己计算recyclerview的高度,比如:
ViewGroup.LayoutParams mParams = recyclerView.getLayoutParams();mParams.height = (CommonUtils.getScreenWidthPX(getActivity()) * 480 / 720 + CommonUtils.dipToPixels(40)) * num + CommonUtils.dipToPixels(8);mParams.width = CommonUtils.getScreenWidthPX(getActivity());recyclerView.setLayoutParams(mParams);
这中方法适合item高度比较好计算的情形,但要遇到里面的item高度不一定这就需要我们重写recyclerview的高度了,以前嵌套listview的时候我们只需重写listview 然后重写
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubint expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, expandSpec);}
但是这种方法在recyclerview重写不管用。
我们此时要重写的的是LinearLayoutManager或GridLayoutManager
public class FullyLinearLayoutManager extends LinearLayoutManager {private static final String TAG = FullyLinearLayoutManager.class.getSimpleName();public FullyLinearLayoutManager(Context context) {super(context);}public FullyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {super(context, orientation, reverseLayout);}private int[] mMeasuredDimension = new int[2];@Overridepublic void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,int widthSpec, int heightSpec) {final int widthMode = View.MeasureSpec.getMode(widthSpec);final int heightMode = View.MeasureSpec.getMode(heightSpec);final int widthSize = View.MeasureSpec.getSize(widthSpec);final int heightSize = View.MeasureSpec.getSize(heightSpec);Log.i(TAG, "onMeasure called. \nwidthMode " + widthMode+ " \nheightMode " + heightSpec+ " \nwidthSize " + widthSize+ " \nheightSize " + heightSize+ " \ngetItemCount() " + getItemCount());int width = 0;int height = 0;for (int i = 0; i < getItemCount(); i++) {measureScrapChild(recycler, i,View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),mMeasuredDimension);if (getOrientation() == HORIZONTAL) {width = width + mMeasuredDimension[0];if (i == 0) {height = mMeasuredDimension[1];}} else {height = height + mMeasuredDimension[1];if (i == 0) {width = mMeasuredDimension[0];}}}switch (widthMode) {case View.MeasureSpec.EXACTLY:width = widthSize;case View.MeasureSpec.AT_MOST:case View.MeasureSpec.UNSPECIFIED:}switch (heightMode) {case View.MeasureSpec.EXACTLY:height = heightSize;case View.MeasureSpec.AT_MOST:case View.MeasureSpec.UNSPECIFIED:}setMeasuredDimension(width, height);}private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,int heightSpec, int[] measuredDimension) {try {View view = recycler.getViewForPosition(0);//fix 动态添加时报IndexOutOfBoundsExceptionif (view != null) {RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,getPaddingLeft() + getPaddingRight(), p.width);int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,getPaddingTop() + getPaddingBottom(), p.height);view.measure(childWidthSpec, childHeightSpec);measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;recycler.recycleView(view);}} catch (Exception e) {e.printStackTrace();} finally {}}}
public class FullyGridLayoutManager extends GridLayoutManager {private int mwidth = 0;private int mheight = 0;public FullyGridLayoutManager(Context context, int spanCount) {super(context, spanCount);}public FullyGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {super(context, spanCount, orientation, reverseLayout);}private int[] mMeasuredDimension = new int[2];public int getMwidth() {return mwidth;}public void setMwidth(int mwidth) {this.mwidth = mwidth;}public int getMheight() {return mheight;}public void setMheight(int mheight) {this.mheight = mheight;}@Overridepublic void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {final int widthMode = View.MeasureSpec.getMode(widthSpec);final int heightMode = View.MeasureSpec.getMode(heightSpec);final int widthSize = View.MeasureSpec.getSize(widthSpec);final int heightSize = View.MeasureSpec.getSize(heightSpec);int width = 0;int height = 0;int count = getItemCount();int span = getSpanCount();for (int i = 0; i < count; i++) {measureScrapChild(recycler, i,View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),mMeasuredDimension);if (getOrientation() == HORIZONTAL) {if (i % span == 0) {width = width + mMeasuredDimension[0];}if (i == 0) {height = mMeasuredDimension[1];}} else {if (i % span == 0) {height = height + mMeasuredDimension[1];}if (i == 0) {width = mMeasuredDimension[0];}}}switch (widthMode) {case View.MeasureSpec.EXACTLY:width = widthSize;case View.MeasureSpec.AT_MOST:case View.MeasureSpec.UNSPECIFIED:}switch (heightMode) {case View.MeasureSpec.EXACTLY:height = heightSize;case View.MeasureSpec.AT_MOST:case View.MeasureSpec.UNSPECIFIED:}setMheight(height);setMwidth(width);setMeasuredDimension(width, height);}private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,int heightSpec, int[] measuredDimension) {if (position < getItemCount()) {try {View view = recycler.getViewForPosition(0);//fix 动态添加时报IndexOutOfBoundsExceptionif (view != null) {RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,getPaddingLeft() + getPaddingRight(), p.width);int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,getPaddingTop() + getPaddingBottom(), p.height);view.measure(childWidthSpec, childHeightSpec);measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;recycler.recycleView(view);}} catch (Exception e) {e.printStackTrace();}}}}
重写完之后,用就好说了,在adapter的onBindview和平常一样用就可以了
final FullyGridLayoutManager manager = new FullyGridLayoutManager(context.getActivity(), 3);manager.setOrientation(GridLayoutManager.VERTICAL);manager.setSmoothScrollbarEnabled(true);viewHolder.recyclerView.setLayoutManager(manager);
或者再activity中设置一样的
此种方法在4.x系统上好用,能显示滑动也流畅,但是在5.x上虽然显示正常,但是滑动的时候好像被粘住了,没有惯性效果。。。。然后郁闷了一下午。。。。
最后解决方法是重写最外层的Scrollview
-
*** 屏蔽 滑动事件* Created by fc on 2015/7/16.*/public class MyScrollview extends ScrollView {private int downX;private int downY;private int mTouchSlop;public MyScrollview(Context context) {super(context);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}public MyScrollview(Context context, AttributeSet attrs) {super(context, attrs);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}public MyScrollview(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}@Overridepublic boolean onInterceptTouchEvent(MotionEvent e) {int action = e.getAction();switch (action) {case MotionEvent.ACTION_DOWN:downX = (int) e.getRawX();downY = (int) e.getRawY();break;case MotionEvent.ACTION_MOVE:int moveY = (int) e.getRawY();if (Math.abs(moveY - downY) > mTouchSlop) {return true;}}return super.onInterceptTouchEvent(e);}}
这篇关于ScrollView,ListView,GrideView,RecyclerView,ViewPager等多种view嵌套问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!