参考:1、冲突解决方案http://blog.csdn.net/issingleman/article/details/51182209
2、很清晰的分发机制流程(图文)http://www.open-open.com/lib/view/open1468806499405.html
Ultra-Pull-To-Refresh这个下拉刷新组件由于其扩展性好,易用性强的特点正被越来越多的开发者使用。但作者忽略了一点,在其content里添加ViewPager时,两个组件手势滑动产生冲突,ViewPager滑动十分困难。借鉴第一篇博客,自定义一个继承PtrFrameLayout的类用来优化事件的分发。亲测有效,代码如下:
import android.content.Context;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import in.srain.cube.views.ptr.PtrClassicDefaultHeader;
import in.srain.cube.views.ptr.PtrFrameLayout;
public class PtrHTFrameLayout extends PtrFrameLayout {
private PtrClassicDefaultHeader mPtrClassicHeader;
private float startY;
private float startX;
// 记录viewPager是否拖拽的标记
private boolean mIsHorizontalMove;
// 记录事件是否已被分发
private boolean isDeal;
private ViewPager mViewPager;
private int mTouchSlop;
public PtrHTFrameLayout(Context context) {
super(context);
initViews();
}
public PtrHTFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initViews();
}
public PtrHTFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initViews();
}
private void initViews() {
mPtrClassicHeader = new PtrClassicDefaultHeader(getContext());
setHeaderView(mPtrClassicHeader);
addPtrUIHandler(mPtrClassicHeader);
}
public PtrClassicDefaultHeader getHeader() {
return mPtrClassicHeader;
}
/**
* Specify the last update time by this key string
*
* @param key
*/
public void setLastUpdateTimeKey(String key) {
if (mPtrClassicHeader != null) {
mPtrClassicHeader.setLastUpdateTimeKey(key);
}
}
/**
* Using an object to specify the last update time.
*
* @param object
*/
public void setLastUpdateTimeRelateObject(Object object) {
if (mPtrClassicHeader != null) {
mPtrClassicHeader.setLastUpdateTimeRelateObject(object);
}
}
public void setViewPager(ViewPager viewPager){
this.mViewPager = viewPager;
if (mViewPager == null){
throw new IllegalArgumentException("viewPager can not be null");
}
//ViewConfiguration这个类主要定义了UI中所使用到的标准常量,
//像超时、尺寸、距离,如果我们需要得到这些常量的数据,我们就可以通过这个类来获取
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
//触发移动事件的最短距离,如果小于这个距离就不触发移动控件,viewpager就是用这个距离来判断用户是否翻页
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mViewPager == null){
return super.dispatchTouchEvent(ev);
}
int action = ev.getAction();
switch (action){
case MotionEvent.ACTION_DOWN:
//记录手指按下的位置
startX = ev.getX();
startY = ev.getY();
//初始化标记
mIsHorizontalMove = false;
isDeal = false;
break;
case MotionEvent.ACTION_MOVE:
// 如果已经判断出是否由横向还是纵向处理,则跳出
if (isDeal){
break;
}
/**拦截禁止交给Ptr的 dispatchTouchEvent处理**/
mIsHorizontalMove = true;
//获取当前手指位置
float endX = ev.getX();
float endY = ev.getY();
float distanceX = Math.abs(endX - startX);
float distanceY = Math.abs(endY - startY);
if (distanceX != distanceY){
// 如果X轴位移大于Y轴位移,那么将事件交给viewPager处理.
if (distanceX>mTouchSlop && distanceX>distanceY){
mIsHorizontalMove = true;
isDeal = true;
}else if (distanceY>mTouchSlop){
mIsHorizontalMove = false;
isDeal = true;
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//下拉刷新状态时如果滚动了viewpager
// 此时mIsHorizontalMove为true 会导致PtrFrameLayout无法恢复原位
mIsHorizontalMove = false;
isDeal = false;
break;
}
if (mIsHorizontalMove){
return dispatchTouchEventSupper(ev);
}
return super.dispatchTouchEvent(ev);
}
}
这里在原作者的基础上补充了mTouchSlop这个变量的作用。另外,如果有小伙伴是把ViewPager封装在一个控件里的,例如有些广告图片自动轮播控件,就在这个控件里面稍作修改。加入ViewPager的Get、Set方法即可。
mPtrFrame.setViewPager(imageCycleView.getmAdvPager());
注意:初始化广告轮播控件必须要加在初始化PtrHTFrameLayout的之前,不然会报空指针异常
如果还有小伙伴没有看懂以上代码的建议去看看文章开头的第二篇博客,对Android的事件分发写得十分详细。这里也稍作总结复习复习:
对于ACTION_DOWN总结
- 对于dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
- ViewGroup想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
- ViewGroup的拦截器onInterceptTouchEvent 默认是不拦截的,所以return super.onInterceptTouchEvent()=return false;
- View没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。
对于ACTION_MOVE、ACTION_UP总结
ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。