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

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

(0)

相关推荐

  • Android Scroller大揭秘

    在学习使用Scroller之前,需要明白scrollTo().scrollBy()方法. 一.View的scrollTo().scrollBy() scrollTo.scrollBy方法是View中的,因此任何的View都可以通过这两种方法进行移动.首先要明白的是,scrollTo.scrollBy滑动的是View中的内容(而且还是整体滑动),而不是View本身.我们的滑动控件如SrollView可以限定宽.高大小,以及在布局中的位置,但是滑动控件中的内容(或者里面的childView)可以是无

  • 详解Android Scroller与computeScroll的调用机制关系

    Android ViewGroup中的Scroller与computeScroll的有什么关系? 答:没有直接的关系 知道了答案,是不是意味着下文就没必要看了,如果说对ViewGroup自定义控件不感兴趣,可以不用看了. 1.Scroller到底是什么? 答:Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是滑动辅助UI运动,反而是单纯地为滑动提供计算. 无论从构造方法还是其他方法,以及Scroller的属性可知,其并不会持有View,辅助ViewGrou

  • 深入理解Android中Scroller的滚动原理

    View的平滑滚动效果 什么是实现View的平滑滚动效果呢,举个简单的例子,一个View从在我们指定的时间内从一个位置滚动到另外一个位置,我们利用Scroller类可以实现匀速滚动,可以先加速后减速,可以先减速后加速等等效果,而不是瞬间的移动的效果,所以Scroller可以帮我们实现很多滑动的效果. 首先我们先来看一下Scroller的用法,基本可概括为"三部曲": 1.创建一个Scroller对象,一般在View的构造器中创建: public ScrollViewGroup(Cont

  • Android程序开发之UIScrollerView里有两个tableView

    一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController : UIViewController <UIScrollViewDelegate,UITableViewDelegate,UITableViewDataSource> { UIScrollView *_scrolView; UITableView *_tableView; UITableView

  • android使用 ScrollerView 实现 可上下滚动的分类栏实例

    如果不考虑更深层的性能问题,我个人认为ScrollerView还是很好用的.而且单用ScrollerView就可以实现分类型的RecyclerView或ListView所能实现的效果. 下面我单单从效果展示方面考虑,使用ScrollerView实现如下图所示的可滚动的多条目分类,只是为了跟大家一起分享一下新思路.(平时:若从复用性等方面考虑,这显然是存在瑕疵的~) 特点描述: 1.可上下滚动 2.有类似于网格布局的样式 3.子条目具有点击事件 刚看到这个效果时,首先想到的是使用分类型的Recyc

  • Android Scroller完全解析

    在Android中,任何一个控件都是可以滚动的,因为在View类当中有scrollTo()和scrollBy()这两个方法,如下图所示: 这两个方法的主要作用是将View/ViewGroup移至指定的坐标中,并且将偏移量保存起来.另外: mScrollX 代表X轴方向的偏移坐标 mScrollY 代表Y轴方向的偏移坐标 这两个方法都是用于对View进行滚动的,那么它们之间有什么区别呢?简单点讲,scrollBy()方法是让View相对于当前的位置滚动某段距离,而scrollTo()方法则是让Vi

  • 详解Android应用开发中Scroller类的屏幕滑动功能运用

    今天给大家介绍下Android中滑屏功能的一个基本实现过程以及原理初探,最后给大家重点讲解View视图中scrollTo 与scrollBy这两个函数的区别 .   首先 ,我们必须明白在Android View视图是没有边界的,Canvas是没有边界的,只不过我们通过绘制特定的View时对Canvas对象进行了一定的操作,例如 : translate(平移).clipRect(剪切)等,以便达到我们的对该Canvas对象绘制的要求 ,我们可以将这种无边界的视图称为"视图坐标"----

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

    Android事件拦截机制 Android中事件的传递和拦截和View树结构是相关联的,在View树中,分为叶子节点和普通节点,普通节点有子节点只能是ViewGroup,叶子节点可以是View或者ViewGroup.Android和事件分发拦截相关的方法有 dispatchTouchEvent(MotionEvent ev) 事件分发相关的方法,沿着View树将一个用户的触摸事件向下分发. onInterceptTouchEvent(MotionEvent ev) 在dispatchTouchE

  • Android Flutter实现自定义下拉刷新组件

    目录 前言 改造点 DIY下拉组件样式 刷新时机调整 效果展示 前言 在Flutter开发中官方提供了多平台的下拉刷新组件供开发者使用,例如RefreshIndicator和CupertinoSliverRefreshControl分别适配Android和iOS下拉刷新交互形态.但实际情况中这两者使用情况却不太相同在使用场景就存在差异,RefreshIndicator作为嵌套型下拉组件列表内容作为它的child使用而CupertinoSliverRefreshControl是嵌入在Sliver列

  • Android实现RecyclerView下拉刷新效果

    本文为大家分享了Android实现RecyclerView下拉刷新效果的具体代码,供大家参考,具体内容如下 思路 RealPullRefreshView继承了一个LinearLayout 里面放置了一个刷新头布局,将其margin_top设置为负的刷新头的高度的 再添加一个RecyclerView 触摸事件分发机制,当在特定条件下让RealPullRefreshView拦截触摸事件,否则的话,不拦截,让RecyclerView自己去处理触摸事件 在手指下拉时,定义好不同的状态STATE,在不同状

  • Android SwipeRefreshLayout下拉刷新源码解析

    本文实例为大家分享了SwipeRefreshLayout下拉刷新源码,供大家参考,具体内容如下 1.SwipeRefreshLayout是Google在support v4 19.1版本的library更新的一个下拉刷新组件,实现刷新效果更方便. 弊端:只有下拉 //设置刷新控件圈圈的颜色 swipe_refresh_layout.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_orang

  • android 有阻尼下拉刷新列表的实现方法

    本文将会介绍有阻尼下拉刷新列表的实现,先来看看效果预览: 这是下拉状态: 这是下拉松开手指后listView回滚到刷新状态时的样子: 1. 如何调用 虽然效果图看起来样子不太好看,主要是因为那个蓝色的背景对不对,没关系,这只是一个背景而已,在了解了我们这个下拉刷新列表的实现之后,你就可以很轻松地修改这个背景,从而实现你想要的UI效果!话不多说,下面我们先来讲讲这个下拉刷新列表是如何使用的,这也是我们编写代码所要实现的目标. final PullToRefreshListView eListVie

  • vue下拉刷新组件的开发及slot的使用详解

    "下拉刷新"和"上滑加载更多"功能在前端.尤其是移动端项目中非常重要,这里笔者由曾经做过的vue项目中的"blink"功能和各位探讨下[下拉刷新]组件的开发: 正式开篇 在前端项目的 components 文件夹下新建 pullRefreshView 文件夹,在其中新建组件 index.vue:(它代表"整个屏幕",通过slot插入页面其他内容而不是传统的设置遮罩层触发下拉刷新) 首先需要编写下拉刷新组件的 template,

  • Android中ListView下拉刷新的实现方法实例分析

    本文实例讲述了Android中ListView下拉刷新的实现方法.分享给大家供大家参考,具体如下: ListView中的下拉刷新是非常常见的,也是经常使用的,看到有很多同学想要,那我就整理一下,供大家参考.那我就不解释,直接上代码了. 这里需要自己重写一下ListView,重写代码如下: package net.loonggg.listview; import java.util.Date; import android.content.Context; import android.util.

  • Android RecyclerView设置下拉刷新的实现方法

    Android RecyclerView设置下拉刷新的实现方法 1 集成 SwipeRefreshLayout 1.1 xml布局文件中使用 <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/refresh" android:layout_width = "match_parent" android:layout_height = "match_parent" &g

  • Android中ListView下拉刷新的实现代码

    Android中ListView下拉刷新 实现效果图: ListView中的下拉刷新是非常常见的,也是经常使用的,看到有很多同学想要,那我就整理一下,供大家参考.那我就不解释,直接上代码了. 这里需要自己重写一下ListView,重写代码如下: package net.loonggg.listview; import java.util.Date; import android.content.Context; import android.util.AttributeSet; import a

  • Android中Listview下拉刷新和上拉加载更多的多种实现方案

    listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明. 方案一:添加头布局和脚布局 android系统为listview提供了addfootview和addheadview两个API.这样可以直接自定义一个View,以添加视图的形式实现下来刷新和上拉加载. 实现步骤    1.创建一个类继承ListView:class PullToRefreshListView extends ListView: 2.在构造方法中添加HeadView:addHeaderVi

随机推荐