android学习笔记——View(一)

 参考: 《Android进阶之光》《Android开发艺术探索》

    https://www.jianshu.com/p/06ff0dfeed39

 View 的位置参数

  

View滑动

   layout() 方法

  View 在绘制的时候会调用onLayout() 方法设置i显示的位置,所以我们也可以通过 layout() 方法设置View  的坐标

  这样,这个View 就会随着拖动进行移动

  offsetLeftAndRight() 与 offsetTopAndBottom() 方法

   

  使用的方法也很简单,效果和使用 layout() 是完全一样的。

   layoutParams 方法

   先使用的是 线性布局 的参数设置方法, 如果使用的是其他布局,如RelativeLayout, 那么类型也应该是 Relativelayout.LayoutParams

   ViewGroup 的参数修改与上面类似。 

   需要注意的是,这两种方法,在点击的时候会鬼畜,(我认为是点击时获取的是LinearLayout子布局的坐标,而不是点击处的坐标导致)

      而且拖动时会影响其他View,如vertical 线性布局中,上下拖动,则会导致其他View 也随之上下滑动(前文的其他方法并不会这样)

  scrollTo() 与 scrollBy() 方法

  scrollTo(x,y)  (绝对移动) 表示移动到 x,y 坐标处

  scrollBy(dx,dy) (相对移动) 表示分别在 x,y 方向移动 dx, dy 距离

  事实上,scrollBy  内部 也是计算好了终点位置,然后调用scrollTo() 方法

 case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                ((View)getParent()).scrollBy(-offsetX,-offsetY);
            // 这里获取父布局,会带着父布局中所有View一起移动,
         // 如果不获取父布局,则会很轻易的把View 滑出其占有的空间,而消失在屏幕上
         // 因为scrollTo 和 scrollBy 只能改变View 内容的位置,而不是view的位置

  使用方法非常简单。但要注意 偏移量使用的是相反数。 

  因为这里的偏移是指手机屏幕的偏移,所以相对View位置的移动,这里应该输入相反数表示反方向移动屏幕

  

  - getX 与 getScrollX 

    getX() 与getY()方法,获得的是View的左上角坐标,当用layout() 等方法时,获取的值会随之改变

    getScrollX 方法则是获取View内容与View 左上角坐标(不动)的相对位置,当使用scroller 或scrollTo 等方法时,其获得值会改变

-  弹性滑动

    - scroller 方式

    典型使用方式:

  流程:

    确定位置参数后,调用了  startScroll() 但是内部什么都没做,只是存储了位置和时间等参数,实际滑动过程是invalidate() 和computeScroll配合完成的

    先说computeScroll() 中的computeScrollOffset 方法: 其内部用 已过时间 和 预计时间比值 ,与移动距离做计算,求出应移动的小距离,然后根据时间是否

  走完,如果走完,表示滑动结束,返回false,如果还未结束,返回true. 

    我们通过 computeScrollOffset ,返回true,则说明还未结束,于是在内部调用 scrollTo(),计算后的位置参数已存储在scroller中,直接使用即可

    postInvalidate 和 invalidate 会让 view 重绘,在view 的draw 方法中又会调用computeScroll , 所以整个过程是一个循环:

      view 绘制 -> draw -> computeScroll (得到位置参数,scrollTo )-> invalidate -> View重绘 -> draw …… 直到完成滑动。

    

  -  延时方式

    以handler为例,用handler的延时方法每次按比例移动View的内容,也可以达到弹性滑动的效果,代码如下:

    

   -  使用动画

    

  只需一句代码就可以实现滑动,需要注意的是,动画同样不会改变View实际的位置,只是挪动内容

 事件分发机制

    事件分发机制的基本思路就是,从顶端View 开始,不消耗则向子View传递事件,一直传递到没有子VIew, 如果还不消耗点击事件,则再依次

    向上传递,由ViewGroup 消耗点击事件;

    

                ( dispatchTouchEvent 伪代码)

  大致含义: 用 onInterceptTouchEvent 判断是否拦截这个事件,如果拦截,调用自己的 onTouchEvent 进行处理,

    如果返回false,,表示不拦截,就传递给子view 的函数再进行分发

          

点击事件,由 Activity -> PhoneWindow -> DecorView -> 根ViewGroup  到ViewGroup ,开始通过DispatchTouchEvent() 方法分发事件

  事件分发的三个主要方法:

    dispatchTouchEvent() :  分发点击事件(传递),把事件传递给子View,如果该ViewGroup没有子View,

            则调用 super.dispatchTouchEvent() ,把事件回给父ViewGroup 处理

    onInterceptTouchEvent() ; 拦截点击事件。(只有ViewGroup 有,View 处于最底层,不需要拦截)

              一旦拦截,就进入onTouchEvent()方法处理点击事件,并结束事件分发。

              不拦截则继续传递。

    onTouchEvent() : 进行事件处理。

     

     在响应点击事件时,不同方法具有不同优先级:onTouchListener > onTouchEvent > onClickListener  

     当处于前方的函数响应点击时,不再走优先级低的函数。

  -  滑动冲突

    滑动冲突解决方案,主要分为 外部拦截法 内部拦截法

    外部拦截法:

      重写父容器的 onInterceptTouchEvent 方法,在  case : MotionEvent.ACTION_MOVE 中 判断是否消耗点击事件。

    核心部分伪代码:

      注意: 在ACTION_DOWN 下,父容器一定要返回false,因为一旦返回true, 接下来的动作都由父容器处理,不再考虑传给子view

           ACITON_UP 也要返回false,因为如果子view消耗了DOWN MOVE等事件,但up被父容器拦截,会导致 onClick事件等无法触发而出现问题,

         而父容器一旦拦截了某个动作,之后的动作也全都由其消耗,所以即使在ACTION_UP返回false,如果在MOVE中拦截了事件,那么最后

          ACTION_UP也一定可以到达父容器。

    内部拦截法:

      父容器不拦截事件,先传递给子元素,子元素决定是否消耗。这种方法需要配合 requestDisallowInterceptTouchEvent 方法使用。

    核心部分伪代码:

      

      这时父容器也要做出对应的修改,要拦截除 ACTION_DOWN外的其他情况,这样当子元素调用 requestDisallowInterceptTouchEvent(false)时 

      父容器才能成功拦截所需事件。

      而 ACTION_DOWN 事件不受 FLAG_DISALLOW_INTERCEPT标记控制,一旦拦截后续事件将无法传给子元素,所以不能拦截ACTION_DOWN;

    内部拦截相对于外部拦截更为复杂,外部拦截法使用更为广泛。其中有两个常见的滑动冲突场景:

    1. 父容器 与 子元素滑动方向互相垂直      

    2. 父容器 与 子元素滑动方向相同

      

    

    1.方向相互垂直时 。 我们在 父容器的 onInterceptTouchEvent 中, ACTION_MOVE时,通过坐标做差,得到纵向和横向的绝对值,

      通过绝对值大小的比较,得出滑动的方向,如果方向是父容器对应的方向,就拦截。关键代码如下:

    

   这样就可以解决了内外滑动方向垂直的冲突

  

     2. 内外滑动方向相同时 

      这里示例情况是 ScrollView 中,有固定大小的头部和一个ListView ,这里解决使用的是内部拦截法

      拦截的思路是:

        优先ListView的滑动,当第一个Item 到达ListView 顶部,并继续向下滑动时,把事件给父布局处理;

        当最后一个Item 到达 ListView 底部,并继续向上滑动时,把事件交给父布局处理。

        调用 requestDisallowInterceptTouchEvent(); 并传入false,表示需要父布局处理滑动;

      关键代码如下:

    

   最终效果如图:

  

猜你喜欢

转载自www.cnblogs.com/xfdmyghdx/p/10723547.html