title: 滑動回彈與內(nèi)層listview的滑動沖突
date: 2016-12-06 10:33:27
tags: problems
- 需求:
1、隨著下拉,view發(fā)生位移,松開回彈到原來的位置
2、內(nèi)部的listview可以正常的上下滑動
3、listview滑到頂部的時(shí)候,繼續(xù)下拉,則是拉動整個(gè)外部view,并且松開回彈
這3個(gè)需求就會造成事件沖突,那么處理方式就是:listview不是初始狀態(tài)就是listview自己處理事件,listview還原到了初始狀態(tài),外部view處理下拉回彈事件。
需求一個(gè)一個(gè)的實(shí)現(xiàn),首先第一個(gè)下拉回彈
因?yàn)槔锩孢€要套一個(gè)listview,所以我們自定義一個(gè)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中的縱坐標(biāo)
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = y;
break;
case MotionEvent.ACTION_MOVE:
moveY = y;
Log.d("myview", "moveY="+moveY+"|startY="+startY);
//當(dāng)往下滑動的時(shí)候
if ((moveY - startY) > 0) {
//獲取到位移距離,并改變布局參數(shù)讓view動起來
layout(getLeft(), getTop() + (moveY - startY), getRight(), getBottom() + (moveY - startY));
//diffY是用來記錄總共的位移數(shù)據(jù)的,用于在ACTION_UP中還原
//每次走進(jìn)move都位移了一點(diǎn)點(diǎn),就重新布局一次,把每次位移的這一點(diǎn)點(diǎn)累加起來
diffY += (moveY - startY);
}
break;
case MotionEvent.ACTION_UP:
// 在ACTION_UP中就不能用moveY-startY了
// 因?yàn)槊看巫叩紸CTION_MOVE的時(shí)候moveY獲取的是觸摸點(diǎn)離view上邊界的距離,在ACTION_MOVE里重新布局后moveY離上邊界肯定是固定的,startY在不放手的情況下也是固定的
// 所以如果用moveY-startY的話會是0,就無法回彈了
layout(getLeft(), getTop() - diffY, getRight(), getBottom() - diffY);
diffY = 0;
break;
}
return true;
}
}
OK,實(shí)現(xiàn)第二條需求,讓內(nèi)部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;
//當(dāng)往下滑動的時(shí)候才攔截,
if ((moveY - startY) > 0) {
isIntercept = true;
}else {
isIntercept = false;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return isIntercept;
}
好,攔截方法寫完了,但這樣的話,所有向下滑動都被我們攔截了,listview就不能向下滑了。
但這是listview的事情,應(yīng)該由listview來做判斷,什么時(shí)候攔截什么時(shí)候不攔截。
自定義一個(gè)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) {
//只有當(dāng)listview還原了,才將事件交給外層,由外層攔截事件
getParent().requestDisallowInterceptTouchEvent(false);
}else {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return super.onTouchEvent(ev);
}
這么寫會發(fā)現(xiàn)無效,因?yàn)閘istview不想scrollview,它沒有重寫getScrollY()方法,直接調(diào)用的是父類view的方法,返回值永遠(yuǎn)是0. 沒寫也沒關(guān)系,我們自己寫
//listview沒有重寫getScrollY方法,我們只能自己寫,可是這方法是final的。。所以不要吐槽名字
public int getScrollY1() {
View v = getChildAt(0);
if (v == null) {
return 0;
}
int firstVisiblePosition = getFirstVisiblePosition();
//top的值肯定是<=0的,因?yàn)榈谝粋€(gè)view完全展示的時(shí)候top為0,滑上去了就是負(fù)數(shù)
int top = v.getTop();
return -top + firstVisiblePosition * v.getHeight() ;
}
將上面的getScrollY()
替換成getScrollY1()
即可。
OK,listview可以正?;瑒恿?,第二條需求完成
大功告成?太年輕了。。
你會發(fā)現(xiàn)可以下拉回彈,listview可以上下滑動并且下拉回彈,但是!
你先把listview往上滑一下,松手,然后再下拉試試
會發(fā)現(xiàn)在臨界狀態(tài)下,外部的view突然往下移動了一大截。
為什么不松手的情況下,listview可以上下滑動,滑到頂了外部view可以正常下拉并回彈,而先滑動一次listview就不行了呢?
因?yàn)槲覀冊谕獠縱iew的onInterceptTouchEvent()
里獲取到了startY,所以當(dāng)listview復(fù)原的時(shí)候的moveY和這個(gè)startY是相等的,外部view就可以正常的下拉回彈。
而先滑動一次listview后,再次點(diǎn)擊滑動,獲取到的是一個(gè)新的startY,而此時(shí)你要把listview復(fù)原的moveY是大于startY的,所以listview滑到頂?shù)臅r(shí)候再下拉,布局會突然下降一截。
知道原因就好解決了,在listview處理滑動事件的時(shí)候,復(fù)原的時(shí)候,將moveY的值給外部view的startY賦值就行了唄!
怎么傳值,請看RxBus工具類,這是用rxjava寫的一個(gè)取代EventBus的工具。
在MyView中初始化的時(shí)候注冊一下
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) {
//這里加一句,將此時(shí)的坐標(biāo)發(fā)給MyView
RxBus.getInstance().send((int)ev.getY(),"startY");
getParent().requestDisallowInterceptTouchEvent(false);
}else {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
OK,全部搞定,收工~