出現(xiàn)嵌套問題吧史,主要是事件的分發(fā)機(jī)制及事件沖突,以下列舉各種沖突及相關(guān)的解決辦法千扶。
一词爬、ScrollView嵌套ListView秃嗜、GrideView等沖突問題的最優(yōu)解決方案
重寫 ListVew或者 GridView
/** 只重寫該方法,達(dá)到使ListView適應(yīng)ScrollView的效果 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
二顿膨、ViewPager和ScrollView嵌套滾動問題解決方案
嵌套是ViewPager-->ScrollView-->ViewPager.
public class HorizontalInnerViewPager extends ViewPager {
/** 觸摸時按下的點(diǎn) **/
PointF downP = new PointF();
/** 觸摸時當(dāng)前的點(diǎn) **/
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());
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);//default
//當(dāng)攔截觸摸事件到達(dá)此位置的時候锅锨,返回true,
//說明將onTouch攔截在此控件虽惭,進(jìn)而執(zhí)行此控件的onTouchEvent
//return true;
//接近水平滑動時子控件處理該事件橡类,否則交給父控件處理
//return mGestureDetector.onTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
//每次進(jìn)行onTouch事件都記錄當(dāng)前的按下的坐標(biāo)
curP.x = ev.getX();
curP.y = ev.getY();
if(ev.getAction() == MotionEvent.ACTION_DOWN){
//記錄按下時候的坐標(biāo)
//切記不可用 downP = curP 蛇尚,這樣在改變curP的時候芽唇,downP也會改變
downP.x = ev.getX();
downP.y = ev.getY();
//此句代碼是為了通知他的父ViewPager現(xiàn)在進(jìn)行的是本控件的操作,不要對我的操作進(jìn)行干擾
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現(xiàn)在進(jìn)行的是本控件的操作,不要對我的操作進(jìn)行干擾
getParent().requestDisallowInterceptTouchEvent(true);
}else{
//接近垂直滑動谱邪,交給父控件處理
getParent().requestDisallowInterceptTouchEvent(false);
}
}
return super.onTouchEvent(ev);
}
private class XScrollDetector extends GestureDetector.SimpleOnGestureListener{
@Override
public 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嵌套咆课,兩個以上還有待改進(jìn),能套兩個就已經(jīng)能滿足很多需求了目前為縱向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;
@Override
public 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) {
// 把滾動事件恢復(fù)給父Scrollview
setParentScrollAble(true);
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
}
}
return super.onInterceptTouchEvent(ev);
}
@Override
public 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) {
// 如果向下滑動到頭扯俱,就把滾動交給父Scrollview
setParentScrollAble(true);
return false;
} else {
setParentScrollAble(false);
}
} else if (currentY > y) {
if (scrollY >= height) {
// 如果向上滑動到頭书蚪,就把滾動交給父Scrollview
setParentScrollAble(true);
return false;
} else {
setParentScrollAble(false);
}
}
currentY = y;
}
}
return super.onTouchEvent(ev);
}
/**
* 是否把滾動事件交給父scrollview
*
* @param flag
*/
private void setParentScrollAble(boolean flag) {
parentScrollView.requestDisallowInterceptTouchEvent(!flag);
}
}
四、RecyclerView嵌套滾動問題()
問題:如果直接在垂直滾動的 View 里面使用水平滾動的 View迅栅,則滾動操作并不是很流暢殊校。
原因:RecyclerView 使用的是垂直滾動的 LinearLayoutManager 布局管理器,而里面每個 Item 為另外一個 RecyclerView 使用的是水平滾動的 LinearLayoutManager读存。而在 Android系統(tǒng)的事件分發(fā) 中为流,即使最上層的 View 只能垂直滾動呕屎,當(dāng)用戶水平拖動的時候,最上層的 View 依然會攔截點(diǎn)擊事件敬察。下面是 RecyclerView.java 中 onInterceptTouchEvent 的相關(guān)代碼:
@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静汤,這樣實(shí)現(xiàn)是沒有問題的琅催。如果我們在里面再放一個 水平滾動的 RecyclerView ,則就出現(xiàn)問題了虫给。
可以通過如下的方式來修復(fù)該問題:
if(canScrollVertically && Math.abs(dy) > mTouchSlop && (canScrollHorizontally || Math.abs(dy) > Math.abs(dx))) {...}
下面是一個完整的實(shí)現(xiàn) BetterRecyclerView.java :
五藤抡、垂直RecyclerView嵌套水平RecyclerView
問題:如果用戶在快速滑動一個子的水平 RecyclerView,在子 RecyclerView 還在滑動的過程中抹估,如果用戶垂直滑動缠黍,則是無法垂直滑動的。原因是子 RecyclerView 依然處理了這個垂直滑動事件药蜻。
解決辦法:針對我們這個嵌套的情況瓷式,父 RecyclerView 應(yīng)該只攔截垂直滾動事件,所以可以這么修改父 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);
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
/* do nothing */
}
}
六语泽、recycleview跟scrollview嵌套問題
問題:scrollview 嵌套recyclerview 時贸典,recyclerview不顯示。
解決辦法:重寫最外層的Scrollview
**
* @描述 屏蔽 滑動事件
*/
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();
}
@Override
public 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嵌套的子布局的點(diǎn)擊事件監(jiān)聽不到
原因:子布局的焦點(diǎn)被占用了
解決辦法:在 ScrollView最外層的LinearLayout(或RelativeLayout等)的布局里添加一行代碼(同理 在RecyclerView的子布局里也是添加這行代碼):
android:descendantFocusability="blocksDescendants"
如下:
<ScrollView
android:id="@+id/goods_view_sv_scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants">
關(guān)于android:descendantFocusability的屬性
該屬性是當(dāng)一個為view獲取焦點(diǎn)時廊驼,定義viewGroup和其子控件兩者之間的關(guān)系。
屬性的值有三種:
- beforeDescendants:viewgroup會優(yōu)先其子類控件而獲取到焦點(diǎn)
- afterDescendants:viewgroup只有當(dāng)其子類控件不需要獲取焦點(diǎn)時才獲取焦點(diǎn)
- blocksDescendants:viewgroup會覆蓋子類控件而直接獲得焦點(diǎn)
八惋砂、viewpager里面嵌套使用listview或者ExpandableList時妒挎,在移出子view時報(bào)IllegalArgumentExcept
原因:這個是因?yàn)?.0系統(tǒng)對viewpage的兼容問題導(dǎo)致的,因?yàn)関iewpager在移除listview或者expandableList時會調(diào)用一次listview(expandableList)的unregisterDataSetObserver方法西饵,而listview(expandableList)本身也會調(diào)用這個方法酝掩,解決方法就是復(fù)寫adapter的unregisterDataSetObserver方法,判斷一下如果observer是null就不執(zhí)行了:
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
if(observer != null)
super.unregisterDataSetObserver(observer);
}
九眷柔、Scrollview嵌套高度自適應(yīng)的ViewPager
網(wǎng)上很多辦法都是選取ViewPager中最大的一個View的高度來顯示期虾,這樣會留白,不算是自適應(yīng)驯嘱,而我這里很好解決
解決辦法:重寫ViewPager
public class MeasureViewPage extends ViewPager{
private int current;
private int height = 0;
private boolean scrollble = true;
public MeasureViewPage(Context context) {
super(context);
}
public MeasureViewPage(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (getChildCount() > current) {
View child = getChildAt(current);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void resetHeight(int current) {
//resetHeight()重置viewpager的高度的方法
this.current = current;
if (getChildCount() > current) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
if (layoutParams == null) {
layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height);
} else {
layoutParams.height = height;
}
setLayoutParams(layoutParams);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!scrollble) {
return true;
}
return super.onTouchEvent(ev);
}
public boolean isScrollble() {
return scrollble;
}
public void setScrollble(boolean scrollble) {
this.scrollble = scrollble;
}
}
Acitivity中調(diào)用如下:
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
pager.voidresetHeight(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
pager.voidresetHeight(0);