Android Scroller及下拉刷新组件原理解析

 更新时间:2017年01月22日 09:56:53   作者:自在时刻  
这篇文章主要为大家详细解析了Android Scroller及下拉刷新组件原理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

Android事件拦截机制

Android中事件的传递和拦截和View树结构是相关联的,在View树中,分为叶子节点和普通节点,普通节点有子节点只能是ViewGroup,叶子节点可以是View或者ViewGroup。Android和事件分发拦截相关的方法有
dispatchTouchEvent(MotionEvent ev)
事件分发相关的方法,沿着View树将一个用户的触摸事件向下分发。
onInterceptTouchEvent(MotionEvent ev)
在dispatchTouchEvent中被调用,用来判断某一层级是否拦截一个事件,返回true即拦截,事件不会再向下分发,注意View树中叶子节点(View和ViewGroup)直接拦截事件。
onTouchEvent(MotionEvent ev)
一个某一个层级拦截了事件,那么所有事件序列都会交由它处理,后面onInterceptTouchEvent不会再被调用,转而onTouchEvent被调用。OnTouchEvent返回true则消耗掉这个事件序列,如果没有消耗ACTION_DOWN事件则事件序列将沿着View树向上传递,去找能处理这个事件的父View。如果消耗了ACTION_DOWN而没有消耗其它事件,那么这个事件序列将消失。

整体过程描述:事件产生传递到某一个ViewGroup时,首先其onInterceptTouchEvent会被调用,如果当前ViewGroup选择拦截这个事件则返回true,于是它的onTouchEvent会被调用。否则将继续调用子View的dispatchTouchEvent进行方法的拦截判断和相应的处理。
当一个View处理事件时,首先会调用它的OnTouchListener,如果OnTouchListener返回false则会继续调用onTouchEvent,在onTouchEvent中才会检查onClickListener,由此可见三种处理事件方法的优先级是:OnTouchListener > onTouchEvent > onClickListener。

ScrollTo,ScrollBy,Scroller

在实现滑动效果的时候,最常用的三个方法就是ScrollTo,ScrollBy和Scroller
首先介绍ScrollTo和ScrollBy,两个方法一个是滑动到某个位置,一个是滑动多少位置。关键在于,ScrollTo和ScrollBy对于普通的View组件比如TextView、ImageView的效果是移动View的内容,也就是相应的字体、照片,仅对于ViewGroup才是移动所有的子View。也就是说,ScrollTo和ScrollBy通常用在自定义的ViewGroup实现滑动效果时。
其次要理解ViewGroup滑动的坐标系,如下图左边是滑动前的布局,一个ViewGroup下面有两个子View,在ViewGroup中调用ScrollTo(0,300)就是将ViewGroup向下滑动,可以将ViewGroup看做一个透明窗口,向下滑动后第一个子View消失不见,第二个子View相对效果即是向上滑动。所以这里要注意ScrollTo和ScrollBy的正负值,同时记住滑动的是ViewGroup,子View只是间接滑动的。
最后,Scroller很简单,Scroller更类似于动画中的插值器,处理计算和存储坐标值,什么也没有做。当我们调用
mScroller.startScroll(getScrollX(),getScrollY(),0,mHeaderHeight+getPaddingTop(),3000);

后,实际上是在其中根据时间和要移动的像素计算出每一时刻所应该在的像素位置,然后不停的调用scrollBy移动到这个位置并重绘。同时由于View在重绘时绘调用computeScroll方法,所以我们要在其中进行判断并继续scroll,形成有条件递归,形成动画。

下拉刷新组件的简单原理

基本介绍

一个典型的下拉刷新界面如上,对于下拉刷新功能而言,界面主要包含两个部分,一个是展示Refresh界面的部分,一个是展示如ListView之类列表的部分。为了实现下拉刷新功能,我们所需要的就是自定义一个ViewGroup。我们的RefreshLayout中包含两个子View,header和content。header界面如下:

content可以是ListView,同样也是一个ViewGroup。界面初始时由于header和content都可以看到,所以我们在RefreshLayout的onLayout方法结束前,调用scrollTo(0,headerHeight)可以将header滑动出界面。然后,总的思路就是分析RefreshLayout和ListView对于一个触摸事件,谁来拦截谁来处理的问题。

RefreshLayout实现:

RefreshLayout绘制过程:

首先通过 LayoutInflater.from(context).inflate以及addView方法,在RefreshLayout构造函数中向布局添加header和content。对于一个ViewGroup而言,绘制过程中最重要的是onMeasure和onLayout方法。
onMeasure

@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  int width = MeasureSpec.getSize(widthMeasureSpec);
  int height = 0;
  for(int i=0;i<getChildCount();i++) {
   measureChild(getChildAt(i),widthMeasureSpec,heightMeasureSpec);
   height += getChildAt(i).getMeasuredHeight();
  }
  height = heightMeasureSpec;
  setMeasuredDimension(width,height);
 }

onMeasure方法中,一定要对全部子View进行measure,在这里调用的是measureChild方法,因为measureChild内部还会根据子View的LayoutParams进一步封装出MeasureSpec进行测量。

@Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
  int count = getChildCount();
  int left =getPaddingLeft();
  Log.d("TAG", l + " " + t + " " + r + " " + b);
  int top = getPaddingTop();
  for(int i=0;i<count;i++) {
   View child = getChildAt(i);
   child.layout(left,top,child.getMeasuredWidth(),child.getMeasuredHeight() + top);
   Log.d("TAG", "child: " + child.getMeasuredWidth() + " " + child.getMeasuredHeight());
   top += child.getMeasuredHeight();
  }
  if(!init){
   //将ViewGroup向y轴正方向移动,其实相当于将View向y轴负方向移动
   scrollTo(0,mHeaderHeight+getPaddingTop());
   invalidate();
   init = true;
  }

 }

onLayout方法中进行我们想要的布局,注意由于重新绘制时,onMeasure和onLayout会多次被调用,所以要注意一些初始化方法的执行。

RefreshLayout事件拦截及处理

@Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  switch (ev.getAction()) {
   case MotionEvent.ACTION_DOWN:
    prevY = (int) ev.getRawY();
    break;
   case MotionEvent.ACTION_MOVE:
    int delY = (int) (ev.getRawY() - prevY);
    Log.d("TAG", "delY " + delY);
    if(delY>0) {
     return true;
    }
    break;
  }
  return false;
 }

在拦截事件中,只做了一个简单的判断,一旦滑动的纵向距离大于0,表明手指再从上向下滑,同时这里应该判断一下ListView中显示的第一条是不是全部数据中的第一条。然后拦截事件后交由onTouchEvent处理。

@Override
 public boolean onTouchEvent(MotionEvent event) {
  switch (event.getAction()) {
   case MotionEvent.ACTION_MOVE:
    int dy = (int) (event.getRawY() - prevY);
    int sy = mHeaderHeight-dy;
    scrollTo(0,sy>0?sy:0);
    Log.d("TAG", "dy " + dy);
    break;
   case MotionEvent.ACTION_UP:
    refresh();
    break;
  }
  return true;
 }

之前将ViewGroup向下滑动了headerHeight的距离,为了让header显示出来,其实应该让ViewGroup向上滑动也即y轴变小,同时为了避免过分滑动还要进行一下判断。当手指抬起时,还要根据移动的y轴增量判断一下是否是有效的滑动,然后处理响应的业务逻辑。注意的是,由于当前是主线程,所以要使用

  new Thread(new Runnable() {
   @Override
   public void run() {
    mission();
    post(new Runnable() {
     @Override
     public void run() {
      mScroller.startScroll(getScrollX(),getScrollY(),0,mHeaderHeight+getPaddingTop(),3000);
      mArrowView.setVisibility(VISIBLE);
      mProgress.setVisibility(GONE);
     }
    });
   }
  }).start();

新起一个线程完成mission,同时通过当前ViewGroup的消息队列,在任务完成后修改UI。

涉及到的原理大致就是这些,完整的代码可以查看何洪洋老师的博客:
https://github.com/hehonghui/android_my_pull_refresh_view

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Android实现拼多多地址选择器

    Android实现拼多多地址选择器

    这篇文章主要为大家详细介绍了Android实现拼多多地址选择器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-05-05
  • Android Spinner和GridView组件的使用示例

    Android Spinner和GridView组件的使用示例

    Spinner其实是一个列表选择框,不过Android的列表选择框并不需要显示下拉列表,而是相当于弹出一个菜单供用户选择,GridView是一个在二维可滚动的网格中展示内容的控件。网格中的内容通过使用adapter自动插入到布局中
    2022-03-03
  • Flutter 路由插件fluro的使用

    Flutter 路由插件fluro的使用

    使用原生的路由基本上能够满足大部分需求,但如果想要对页面做类似浏览器 url 那样的路由,或者控制页面跳转的转场动画,那么原生的路由需要做不少的改造。在 pub 上,有优秀的路由插件 fluro 解决这类问题。本文介绍该插件的使用方法
    2021-06-06
  • Android从相册选择图片剪切和上传

    Android从相册选择图片剪切和上传

    这篇文章主要为大家详细介绍了Android从相册选择一个图片剪切、上传的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-02-02
  • 代码实例分析android中inline hook

    代码实例分析android中inline hook

    本片文章主要给大家通过代码示例分析了android中inline hook的用法是实现过程,需要的朋友跟着参考下吧。
    2018-01-01
  • 详解Android Ashmem匿名共享内存

    详解Android Ashmem匿名共享内存

    这篇文章主要介绍了Android Ashmem匿名共享内存的相关资料,帮助大家更好的理解和学习使用Android开发,感兴趣的朋友可以了解下
    2021-04-04
  • 简单掌握Android Widget桌面小部件的创建步骤

    简单掌握Android Widget桌面小部件的创建步骤

    这篇文章主要介绍了简单掌握Android Widget桌面小部件的创建步骤,Widget一般采用web前端技术进行开发,需要的朋友可以参考下
    2016-03-03
  • Android实现水波纹外扩效果的实例代码

    Android实现水波纹外扩效果的实例代码

    微信曾经推出了一个查找附近好友的功能,大致功能是这样的:屏幕上有一个按钮,长按按钮的时候,会有一圈圈水波纹的动画向外扩散,松手后,动画结束
    2018-05-05
  • Android显示全文折叠控件使用方法详解

    Android显示全文折叠控件使用方法详解

    这篇文章主要为大家详细介绍了Android显示全文折叠控件的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-11-11
  • Android 实现圆角图片的简单实例

    Android 实现圆角图片的简单实例

    这篇文章主要介绍了Android 实现圆角图片的简单实例的相关资料,Android 圆角图片的实现形式,包括用第三方、也有系统,需要的朋友可以参考下
    2017-07-07

最新评论