最近笔者在做项目的时候遇到一个问题,implements OnTouchListener中重写onTouch事件无法获取ACTION_DOWN中getX的值。
笔者想实现的效果:一个A页面,一个B页面(包含4个fragment的viewpager),viewpager可以实现fragment横滑切换,当fragment为0也就是第一个页面时,通过ontouch接口,在ACTION_DOWN获取用户点击屏幕的X距离StartX,再通过action_move获取用户滑动的距离SlipX,通过当StartX-SlipX>100,实现finish页面。再通过overridePendingTransition(int enterAnim, intexitAnim)设置进入,退出的动画,实现仿QQ横滑退出的效果。
问题描述:在ViewPager绑定onTouch事件中的ACTION_DOWN中无法获取getX的距离,然而action_move,action_up中确能够获取到getX的值。
好了,想必大家也明白了想要达到的效果以及遇到的问题,那么笔者也不废话了。直接说明出现此问题的原因,以及解决方案,最后当然是源码共享。
核心原因:对于(dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent),以及onTouch这几个方法没有完全搞明白,以及其传递机制。于是本着严谨的态度,通过万能的互联网终于搞明白了这几个之间的关系,以及用法,下面这个图很好的说明了该问题。(后面我会贴出相关参考的帖子链接,大家有兴趣可以看一下).
1.首先onTouch方法是继承OnTouchListener接口需要重写的方法,当一个View绑定了该监听的时候,就会调用onTouch方法来监听用户对该View的手指操作.(什么是View?也就是一般的源生控件.例如button,textview,以本案例的viewpager都属于view或者自定义View).
mViewPager.setOnTouchListener(this);// 为ViewPager设置ontouch监听获取滚动距离
在这种情况下,如果我们在Activity里面为一个View控件绑定了setOnTouchListener,那么当屏幕有touch事件的时候,首先会是绑定该监听的View响应onTouch事件,执行onTouch方法.
如果onTouch返回值为true,表示这个touch事件被onTouch方法处理完毕,不会把touch事件再传递给Activity,也就是说activity的dispatchTouchEvent方法不会被调用,在本案例中,也就是viewpager就不会滑动了(有兴趣可以试一下)。
如果onTouch的返回值是false,表示这个touch事件没有被完全处理,onTouch返回以后,touch事件被传递给Activity,activity的dispatchTouchEvent方法被调用.
2.其次dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,如下图所示,简单解释一下吧.
*activity里可以重写dispatchTouchEvent,onTouchEvent两个方法,实现监听.
*viewgroup里可以重写dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三个方法,实现监听.(什么是ViewGroup?也就是一般的放置View的容器,一般是代码自定义button,textview这些)
*view里面可以重写dispatchTouchEvent,onTouchEvent两个方法,实现监听.
3.接下来,就是其传递机制如下图所示
Android中默认情况下事件传递是由最终的view的接收到,传递过程是从父布局到子布局,也就是从Activity到ViewGroup到View的过程,从上向下分发.
(可能这里有些同学觉得有点绕,举个例子,如果在一个activity里面都实现了Activity\ViewGroup\的dispatchTouchEvent方法以及View的Ontouch方法,那么当用户点击屏幕的时候,获取到用户action_down的getX控制台打印顺序,依次是Activity→ViewGroup→View)
(1) 首先会去触发activity里面的dispatchTouchEvent事件
(2)然后是ViewGroup的dispatchTouchEvent事件(一般是代码自定义控件的时候重写该方法才会去执行)
(3) 最后是View的dispatchTouchEvent事件.因为是为View,setOnTouchListener.所以会去执行onTouch方法.
因此这个问题就迎刃而解了,如果说我在onTouch里面无法获取到ACTION_DOWN的getX的值,那么我就重写activity的dispatchTouchEvent方法,在这里直接获取ACTION_DOWN的getX的值,然后在将touch事件传递下去在ontouch获取ACTION_MOVE的getX的值,那么我就能通过当StartX-SlipX>100,实现finish页面了.
不过要注意的地方有两点:
1.当一个touch事件执行完毕,最后return的true;表示这个touch事件处理完毕,不会把touch事件再向下传递.反之,false,则向下传递
2. 在重写dispatchTouchEvent等方法的时候,一定要 super.dispatchTouchEvent(ev);否则事件会就此打住,任凭你如何滑动,屏幕也会一定不动
最后这里直接上核心代码。
/** * 带有4个fragment的ViewPager,并且在第一个fragment的时候实现向右滑动finish当前页面返回上一个页面 * @author max */ public class SecondActivity extends FragmentActivity implements OnClickListener, OnTouchListener, OnPageChangeListener { private ViewPager mViewPager;// 滑动的viewpager private TextView text1, text2, text3, text4;// 点击跳转的fragment页面 private List<Fragment> mTabs = new ArrayList<Fragment>(); // fragment集合 private FragmentPagerAdapter mAdapter; // viewpager的adapter // 记录开始手指点击的位置,和滑动的X距离 private int StartX, SlipX; // 当前的页面 private int currentItemNum = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.second_activity); init(); initDatas(); mViewPager.setAdapter(mAdapter); mViewPager.setOnPageChangeListener(this);// 设置页面滚动监听获取当前页面的页码 mViewPager.setOnTouchListener(this);// 为ViewPager设置ontouch监听获取滚动距离 } /** 位fragment的list传值 **/ private void initDatas() { Fragment1 fragment1 = new Fragment1(); Fragment2 fragment2 = new Fragment2(); Fragment3 fragment3 = new Fragment3(); Fragment4 fragment4 = new Fragment4(); // 往list加入4个碎片 mTabs.add(fragment1); mTabs.add(fragment2); mTabs.add(fragment3); mTabs.add(fragment4); // 初始化适配器 mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public int getCount() { return mTabs.size(); } @Override public Fragment getItem(int arg0) { return mTabs.get(arg0); } }; } /** 初始化控件 **/ private void init() { mViewPager = (ViewPager) findViewById(R.id.viewpage); text1 = (TextView) findViewById(R.id.text1); text2 = (TextView) findViewById(R.id.text2); text3 = (TextView) findViewById(R.id.text3); text4 = (TextView) findViewById(R.id.text4); text1.setOnClickListener(this); text2.setOnClickListener(this); text3.setOnClickListener(this); text4.setOnClickListener(this); } /** 点击text切换子页面 **/ @Override public void onClick(View v) { switch (v.getId()) { case R.id.text1: mViewPager.setCurrentItem(0); break; case R.id.text2: mViewPager.setCurrentItem(1); break; case R.id.text3: mViewPager.setCurrentItem(2); break; case R.id.text4: mViewPager.setCurrentItem(3); break; default: break; } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { // 一定要spuer,否则事件打住,不会在向下调用了 super.dispatchTouchEvent(ev); switch (ev.getAction()) { // 记录用户手指点击的位置 case MotionEvent.ACTION_DOWN: StartX = (int) ev.getX(); Log.i("info", "StartX = " + StartX); break; } return true;// return false,继续向下传递,return true;拦截,不向下传递 } // 默认是重写onTouch事件 @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: StartX = (int) event.getX(); Log.i("info", "StartX11111 = " + StartX); break; case MotionEvent.ACTION_MOVE: // 获取滑动时候的X距离 SlipX = (int) event.getX(); Log.i("info", "1= " + SlipX); if (currentItemNum == 0 && SlipX - StartX > 100) { // 在第一个页面,手势向右滑动,finish当前页面 finish(); // 设置切换切换动画 overridePendingTransition(R.anim.slide_left_in, R.anim.slide_right_out); } break; default: break; } return false; } @Override public void onPageScrollStateChanged(int arg0) { } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageSelected(int arg0) { // 获取当前页码 currentItemNum = arg0; } }
第一次发布博客,稍微有点紧张,如有有纰漏,欢迎各位看官指出,大家一起相互学习
最后感谢万能的互联网,以及各位勤劳的博主,以下贴出参考资料地址,有兴趣的童鞋可以去看看.
http://blog.csdn.net/yanzi1225627/article/details/22592831 (打印出了各事件点击打印日志)
http://blog.csdn.net/xyz_lmn/article/details/12517911 (ontouch事件分发机制图片详解)