title: 滑動回彈與內層listview的滑動沖突
date: 2016-12-06 10:33:27
tags: problems
- 需求:
1蛛勉、隨著下拉孤澎,view發(fā)生位移蛙酪,松開回彈到原來的位置
2、內部的listview可以正常的上下滑動
3熊赖、listview滑到頂部的時候疲牵,繼續(xù)下拉承二,則是拉動整個外部view,并且松開回彈
這3個需求就會造成事件沖突纲爸,那么處理方式就是:listview不是初始狀態(tài)就是listview自己處理事件矢洲,listview還原到了初始狀態(tài),外部view處理下拉回彈事件缩焦。
需求一個一個的實現读虏,首先第一個下拉回彈
因為里面還要套一個listview,所以我們自定義一個view繼承自viewGroup袁滥,這里選擇的是LinearLayout
package com.aidebar.demo;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
/**
* @author xzj
* @date 2016/12/6 10:54.
*/
public class MyView extends LinearLayout {
private int startY;
private int moveY;
private int diffY;
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getY(); //getY()獲取的是按下去的位置在view中的縱坐標
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = y;
break;
case MotionEvent.ACTION_MOVE:
moveY = y;
Log.d("myview", "moveY="+moveY+"|startY="+startY);
//當往下滑動的時候
if ((moveY - startY) > 0) {
//獲取到位移距離盖桥,并改變布局參數讓view動起來
layout(getLeft(), getTop() + (moveY - startY), getRight(), getBottom() + (moveY - startY));
//diffY是用來記錄總共的位移數據的,用于在ACTION_UP中還原
//每次走進move都位移了一點點题翻,就重新布局一次揩徊,把每次位移的這一點點累加起來
diffY += (moveY - startY);
}
break;
case MotionEvent.ACTION_UP:
// 在ACTION_UP中就不能用moveY-startY了
// 因為每次走到ACTION_MOVE的時候moveY獲取的是觸摸點離view上邊界的距離腰鬼,在ACTION_MOVE里重新布局后moveY離上邊界肯定是固定的,startY在不放手的情況下也是固定的
// 所以如果用moveY-startY的話會是0塑荒,就無法回彈了
layout(getLeft(), getTop() - diffY, getRight(), getBottom() - diffY);
diffY = 0;
break;
}
return true;
}
}
OK熄赡,實現第二條需求,讓內部listview可以滑動齿税,要讓子view可以接受到MotionEvent彼硫,首先我們自己就不能攔截,那么重寫onInterceptTouchEvent()
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean isIntercept=false;
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = y;
break;
case MotionEvent.ACTION_MOVE:
moveY = y;
//當往下滑動的時候才攔截凌箕,
if ((moveY - startY) > 0) {
isIntercept = true;
}else {
isIntercept = false;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return isIntercept;
}
好拧篮,攔截方法寫完了,但這樣的話牵舱,所有向下滑動都被我們攔截了串绩,listview就不能向下滑了。
但這是listview的事情芜壁,應該由listview來做判斷礁凡,什么時候攔截什么時候不攔截。
自定義一個MyListView慧妄,繼承自ListView顷牌,并重寫onTouchEvent()
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//down被攔截了,后續(xù)所有事件就都收不到了
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (getScrollY() == 0) {
//只有當listview還原了腰涧,才將事件交給外層,由外層攔截事件
getParent().requestDisallowInterceptTouchEvent(false);
}else {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return super.onTouchEvent(ev);
}
這么寫會發(fā)現無效紊浩,因為listview不想scrollview窖铡,它沒有重寫getScrollY()方法,直接調用的是父類view的方法坊谁,返回值永遠是0. 沒寫也沒關系费彼,我們自己寫
//listview沒有重寫getScrollY方法,我們只能自己寫口芍,可是這方法是final的箍铲。。所以不要吐槽名字
public int getScrollY1() {
View v = getChildAt(0);
if (v == null) {
return 0;
}
int firstVisiblePosition = getFirstVisiblePosition();
//top的值肯定是<=0的鬓椭,因為第一個view完全展示的時候top為0颠猴,滑上去了就是負數
int top = v.getTop();
return -top + firstVisiblePosition * v.getHeight() ;
}
將上面的getScrollY()
替換成getScrollY1()
即可。
OK小染,listview可以正城涛停滑動了,第二條需求完成
大功告成裤翩?太年輕了资盅。。
你會發(fā)現可以下拉回彈,listview可以上下滑動并且下拉回彈呵扛,但是每庆!
你先把listview往上滑一下,松手今穿,然后再下拉試試
會發(fā)現在臨界狀態(tài)下缤灵,外部的view突然往下移動了一大截。
為什么不松手的情況下荣赶,listview可以上下滑動凤价,滑到頂了外部view可以正常下拉并回彈,而先滑動一次listview就不行了呢拔创?
因為我們在外部view的onInterceptTouchEvent()
里獲取到了startY利诺,所以當listview復原的時候的moveY和這個startY是相等的,外部view就可以正常的下拉回彈剩燥。
而先滑動一次listview后慢逾,再次點擊滑動,獲取到的是一個新的startY灭红,而此時你要把listview復原的moveY是大于startY的侣滩,所以listview滑到頂的時候再下拉,布局會突然下降一截变擒。
知道原因就好解決了君珠,在listview處理滑動事件的時候,復原的時候娇斑,將moveY的值給外部view的startY賦值就行了唄策添!
怎么傳值,請看RxBus工具類毫缆,這是用rxjava寫的一個取代EventBus的工具唯竹。
在MyView中初始化的時候注冊一下
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
RxBus.getInstance().toObservable(Integer.class,"startY")
.subscribe(new RxBusSubscriber<Integer>() {
@Override
public void receive(Integer data) {
startY = data;
}
});
}
在MyListView中加一句
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (getScrollY1() == 0) {
//這里加一句,將此時的坐標發(fā)給MyView
RxBus.getInstance().send((int)ev.getY(),"startY");
getParent().requestDisallowInterceptTouchEvent(false);
}else {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
OK苦丁,全部搞定浸颓,收工~