站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础

Android多分辨率适配框架(2)— 原理剖析

Android多分辨率适配框架(3)— 使用指南


自定义View系列教程00–推翻自己和过往,重学自定义View

自定义View系列教程01–常用工具介绍

自定义View系列教程02–onMeasure源码详尽分析

自定义View系列教程03–onLayout源码详尽分析

自定义View系列教程04–Draw源码分析及其实践

自定义View系列教程05–示例分析

自定义View系列教程06–详解View的Touch事件处理

自定义View系列教程07–详解ViewGroup分发Touch事件

自定义View系列教程08–滑动冲突的产生及其处理


PS:如果觉得文章太长,那就直接看视频


在自定义View的时候,常常会用到一些Android系统提供的工具。这些工具封装了我们经常会用到的方法,比如拖拽View,计算滑动速度,View的滚动,手势处理等等。如果我们自己去实现这些方法会比较繁琐,而且容易出一些bug。所以,作为自定义View系列教程的开端,先介绍一下这些常用的工具,以便在后续的学习和工作中使用。

  • Configuration
  • ViewConfiguration
  • GestureDetector
  • VelocityTracker
  • Scroller
  • ViewDragHelper

嗯哼,它们都已经躺在这里了,我们就来挨个瞅瞅


Configuration

This class describes all device configuration information that can

impact the resources the application retrieves.

Configuration用来描述设备的配置信息。

比如用户的配置信息:locale和scaling等等

比如设备的相关信息:输入模式,屏幕大小, 屏幕方向等等

我们经常采用如下方式来获取需要的相关信息:

Configuration configuration=getResources().getConfiguration();
//获取国家码
int countryCode=configuration.mcc;
//获取网络码
int networkCode=configuration.mnc;
//判断横竖屏
if(configuration.orientation==Configuration.ORIENTATION_PORTRAIT){ } else { }

ViewConfiguration

看完Configuration再来瞅ViewConfiguration。这两者的名字有些像,差了一个View;咋一看,还以为它俩是继承关系,其实不然。

官方对于ViewConfiguration的描述是:

Contains methods to standard constants used in the UI for timeouts,

sizes, and distances.

ViewConfiguration提供了一些自定义控件用到的标准常量,比如尺寸大小,滑动距离,敏感度等等。

可以利用ViewConfiguration的静态方法获取一个实例

ViewConfiguration viewConfiguration=ViewConfiguration.get(context);

在此介绍ViewConfiguration的几个对象方法。

ViewConfiguration  viewConfiguration=ViewConfiguration.get(context);
//获取touchSlop。该值表示系统所能识别出的被认为是滑动的最小距离
int touchSlop = viewConfiguration.getScaledTouchSlop();
//获取Fling速度的最小值和最大值
int minimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
int maximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
//判断是否有物理按键
boolean isHavePermanentMenuKey=viewConfiguration.hasPermanentMenuKey();

ViewConfiguration还提供了一些非常有用的静态方法,比如:

//双击间隔时间.在该时间内是双击,否则是单击
int doubleTapTimeout=ViewConfiguration.getDoubleTapTimeout();
//按住状态转变为长按状态需要的时间
int longPressTimeout=ViewConfiguration.getLongPressTimeout();
//重复按键的时间
int keyRepeatTimeout=ViewConfiguration.getKeyRepeatTimeout();

GestureDetector

大家都知道,我们可以在onTouchEvent()中自己处理手势。其实Android系统也给我们提供了一个手势处理的工具,这就是GestureDetector手势监听类。利用GestureDetector可以简化许多操作,轻松实现一些常用的功能。

嗯哼,来吧,一起瞅瞅它是怎么使用的。

第一步:实现OnGestureListener

private class GestureListenerImpl implements GestureDetector.OnGestureListener {
//触摸屏幕时均会调用该方法
@Override
public boolean onDown(MotionEvent e) {
System.out.println("---> 手势中的onDown方法");
return false;
} //手指在屏幕上拖动时会调用该方法
@Override
public boolean onFling(MotionEvent e1,MotionEvent e2, float velocityX,float velocityY) {
System.out.println("---> 手势中的onFling方法");
return false;
} //手指长按屏幕时均会调用该方法
@Override
public void onLongPress(MotionEvent e) {
System.out.println("---> 手势中的onLongPress方法");
} //手指在屏幕上滚动时会调用该方法
@Override
public boolean onScroll(MotionEvent e1,MotionEvent e2, float distanceX,float distanceY) {
System.out.println("---> 手势中的onScroll方法");
return false;
} //手指在屏幕上按下,且未移动和松开时调用该方法
@Override
public void onShowPress(MotionEvent e) {
System.out.println("---> 手势中的onShowPress方法");
} //轻击屏幕时调用该方法
@Override
public boolean onSingleTapUp(MotionEvent e) {
System.out.println("---> 手势中的onSingleTapUp方法");
return false;
}
}

第二步:生成GestureDetector对象

GestureDetector gestureDetector = new GestureDetector(context,new
GestureListenerImpl());

这里的GestureListenerImpl就是GestureListener监听器的实现。

第三步:将Touch事件交给GestureDetector处理

比如将Activity的Touch事件交给GestureDetector处理

@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}

比如将View的Touch事件交给GestureDetector处理

mButton=(Button) findViewById(R.id.button);
mButton.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
});

VelocityTracker

这个玩意儿一看名字,大概就可以猜到意思了。嗯哼,速度追踪。

VelocityTracker用于跟踪触摸屏事件(比如,Flinging及其他Gestures手势事件等)的速率。

简单说一下它的常用套路。

第一步:开始速度追踪

private void startVelocityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}

在这里我们初始化VelocityTracker,并且把要追踪的MotionEvent注册到VelocityTracker的监听中。

第二步:获取追踪到的速度

private int getScrollVelocity() {
// 设置VelocityTracker单位.1000表示1秒时间内运动的像素
mVelocityTracker.computeCurrentVelocity(1000);
// 获取在1秒内X方向所滑动像素值
int xVelocity = (int) mVelocityTracker.getXVelocity();
return Math.abs(xVelocity);
}

同理可以获取1秒内Y方向所滑动像素值

第三步:解除速度追踪

private void stopVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}

以上就是VelocityTracker的常用使用方式。


Scroller

Scroller挺常见的,用的比较多了。在此只强调几个重要的问题,别的就不再赘述了。

第一点:scrollTo()和scrollBy()的关系

先看scrollBy( )的源码

public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}

这就是说scrollBy( )调用了scrollTo( ),最终起作用的是scrollTo( )方法。

第二点:scroll的本质

scrollTo( )和scrollBy( )移动的只是View的内容,而且View的背景是不移动的。

第三点:scrollTo( )和scrollBy( )方法的坐标说明

比如我们对于一个TextView调用scrollTo(0,25) ;那么该TextView中的content(比如显示的文字:Hello)会怎么移动呢?

向下移动25个单位?不!恰好相反!!这是为什么呢?

因为调用该方法会导致视图重绘,即会调用

public void invalidate(int l, int t, int r, int b)

此处的l,t,r,b四个参数就表示View原来的坐标.

在该方法中最终会调用:

tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);

p.invalidateChild(this, tmpr);

其中tmpr是一个Rect,this是原来的View;通过这两行代码就把View在一个Rect中重绘。

请注意第一行代码:

原来的l和r均减去了scrollX

原来的t和b均减去了scrollY

就是说scrollX如果是正值,那么重绘后的View的宽度反而减少了;反之同理

就是说scrollY如果是正值,那么重绘后的View的高度反而减少了;反之同理

所以,TextView调用scrollTo(0,25)和我们的理解相反

scrollBy(int x,int y)方法与上类似,不再多说了.


ViewDragHelper

在项目中很多场景需要用户手指拖动其内部的某个View,此时就需要在onInterceptTouchEvent()和onTouchEvent()这两个方法中写不少逻辑了,比如处理:拖拽移动,越界,多手指的按下,加速度检测等等。

ViewDragHelper可以极大的帮我们简化类似的处理,它提供了一系列用于处理用户拖拽子View的辅助方法和与其相关的状态记录。比较常见的:QQ侧滑菜单,Navigation Drawer的边缘滑动,都可以由它实现。

ViewDragHelper的使用并不复杂,在此通过一个示例展示其常用的用法。

/**
* ViewDragHelper使用示例
* 原创作者:谷哥的小弟
* 原创地址:http://blog.csdn.net/lfdfhl
*/
public class MyLinearLayout extends LinearLayout {
private ViewDragHelper mViewDragHelper; public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initViewDragHelper();
} //初始化ViewDragHelper
private void initViewDragHelper() {
mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
} //处理水平方向的越界
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
int fixedLeft;
View parent = (View) child.getParent();
int leftBound = parent.getPaddingLeft();
int rightBound = parent.getWidth() - child.getWidth() - parent.getPaddingRight(); if (left < leftBound) {
fixedLeft = leftBound;
} else if (left > rightBound) {
fixedLeft = rightBound;
} else {
fixedLeft = left;
}
return fixedLeft;
} //处理垂直方向的越界
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int fixedTop;
View parent = (View) child.getParent();
int topBound = getPaddingTop();
int bottomBound = getHeight() - child.getHeight() - parent.getPaddingBottom();
if (top < topBound) {
fixedTop = topBound;
} else if (top > bottomBound) {
fixedTop = bottomBound;
} else {
fixedTop = top;
}
return fixedTop;
} //监听拖动状态的改变
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
switch (state) {
case ViewDragHelper.STATE_DRAGGING:
System.out.println("STATE_DRAGGING");
break;
case ViewDragHelper.STATE_IDLE:
System.out.println("STATE_IDLE");
break;
case ViewDragHelper.STATE_SETTLING:
System.out.println("STATE_SETTLING");
break;
}
} //捕获View
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
System.out.println("ViewCaptured");
} //释放View
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
System.out.println("ViewReleased");
}
});
} //将事件拦截交给ViewDragHelper处理
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
} //将Touch事件交给ViewDragHelper处理
@Override
public boolean onTouchEvent(MotionEvent ev) {
mViewDragHelper.processTouchEvent(ev);
return true;
}
}

从这个例子可以看出来ViewDragHelper是作用在ViewGroup上的(比如LinearLayout)而不是直接作用到某个被拖拽的子View。其实这也不难理解,因为子View在布局中的位置是其所在的ViewGroup决定的。

在该例中ViewDragHelper做了如下主要操作:

(1) ViewDragHelper接管了ViewGroup的事件拦截,请参见代码第91-94行

(2) ViewDragHelper接管了ViewGroup的Touch事件,请参见代码第98-102行

(3) ViewDragHelper处理了拖拽子View时的边界越界,请参见代码第22-55行

(4) ViewDragHelper监听拖拽子View时的状态变化,请参见代码第58-72行

除了这些常见的操作,ViewDragHelper还可以实现:抽屉拉伸,拖拽结束松手后子View自动返回到原位等复杂操作。


好了,了解完这些非常有用的工具,我们就正式进入自定义View。


PS:

0 如果觉得文章太长,那就直接看视频

1 这个系列大概有8篇

2 onMeasure(),onLayout(),onDraw()源码分析和示例

3 dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()源码分析和示例

4 滑动冲突

5 各种自定义View的实现方式和具体示例

6 力求从源码角度解开心中的疑惑,知其所以然。避免人云亦云

7 每周至少一篇

自定义View系列教程01--常用工具介绍的更多相关文章

  1. 自定义View系列教程08--滑动冲突的产生及其处理

    深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...

  2. 自定义View系列教程07--详解ViewGroup分发Touch事件

    深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...

  3. 自定义View系列教程06--详解View的Touch事件处理

    深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...

  4. 自定义View系列教程05--示例分析

    站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Android多分辨率适配框架(3)- 使用指南 自定 ...

  5. 自定义View系列教程04--Draw源码分析及其实践

    深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...

  6. 自定义View系列教程03--onLayout源码详尽分析

    深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...

  7. 自定义View系列教程02--onMeasure源码详尽分析

    深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...

  8. 封装一个简单好用的打印Log的工具类And快速开发系列 10个常用工具类

    快速开发系列 10个常用工具类 http://blog.csdn.net/lmj623565791/article/details/38965311 ------------------------- ...

  9. 2.Ventuz Designer常用工具介绍

    Ventuz Designer常用工具介绍 1.  打开Ventuz Designer 图1.1 2.  Ventuz Designer第一个界面 图2.1 Recent Projects:最近创建的 ...

随机推荐

  1. day 71作业

    作业: url配置 urlpatterns = [ url(r'^v2/cars/$',views.CarAPIView.as_view()), url(r'^v2/cars/(?P<pk> ...

  2. Django-rest Framework(五)

    把十大接口做完了才能更好的了解后面的视图类 1.(重点)二次封装Response;自定义APIResponse继承Response,重写 ____init____方法 from rest_framew ...

  3. [Array]167. Two Sum II - Input array is sorted

    Given an array of integers that is already sorted in ascending order, find two numbers such that the ...

  4. WPF 的另类资源方式 Resources.resx

      类似Winform的搞法,可以把资源放到Resources.resx中. 1.字符串 打开这个编辑器后,输入Name和Value就可以了. CS代码里面,很简单的调用: var title = W ...

  5. springmvc java程序文件保存地址的路径问题

    会保存为这种斜杠 不论之前填写的是什么样

  6. Web前端开发工程师需要掌握哪些核心技能?

    Web前端开发所涉及的内容主要包括W3C标准中的结构.行为和表现,那么这三项中我们需要掌握的核心技能是什么呢? 1.开发语言 HTML发展历史有二十多年,历经多次版本更新,HTML5和CSS3的出现又 ...

  7. java.lang.IllegalStateException: 1 matchers expected, 5 recorded.

    这是一个很神奇的错误. 常规的出错是因为在mock方法里,其中某一个或者几个参数使用了EasyMock.anyxx(),而其他的使用了具体的值. java.lang.IllegalStateExcep ...

  8. IO流2 --- File类的常用方法1 --- 技术搬运工(尚硅谷)

    File类的获取功能 @Test public void test2(){ File file1 = new File("hello.txt"); File file2 = new ...

  9. Python之路,Day2 - Python基础(转载Alex)

    Day2-转自金角大王 本节内容 列表.元组操作 字符串操作 字典操作 集合操作 文件操作 字符编码与转码 1. 列表.元组操作 列表是我们最以后最常用的数据类型之一,通过列表可以对数据实现最方便的存 ...

  10. python基础--包、logging、hashlib、openpyxl、深浅拷贝

    包:它是一系列模块文件的结合体,表现形式就是一个文件夹,该文件夹内部通常会有一个__init__.py文件,包的本质还是一个模块. 首次导入包:(在导入语句中中 . 号的左边肯定是一个包(文件夹)) ...