第三章 View事件体系(1)
来源:互联网 发布:压实度软件 编辑:程序博客网 时间:2024/06/05 15:55
本文为Android开发艺术探索的笔记,仅供学习
首先View虽然不是四大组件,但是它的作用和重要性甚至比Receiver和Provider要重要的多。View是Android提供的控件的基类,然而这些控件远远不能满足我们日常开发的需求,所以我们需要工具需求去自定义新的空间。一个典型的场景就是滑动屏幕,很多情况下我们的应多都支持滑动操作,当处于不同级别的View都去相应用户的滑动操作的时候,就会带来一个问题。滑动冲突!想要处理这个问题我们就要去理解View的分发机制,如何去分发?如何去拦截?我们会在后续中去讲解。
1 View的基本知识
现在我们先来了解一下View的一些基本知识,以为后面更好的介绍做铺垫。View的位置参数,MotionEvent和TouchSlop对象,VelocityTracker和Scroll对象,以便大家更好的去了解去理解一些复杂的内容。
1.1 什么是View
View,在前面也说了是所有Android控件的基类,不管是Button,TextView还是复杂的RelativeLayout它们的基类都是View。除了View,还有ViewGroup,从名字上看ViewGroup里面会有很多个View,对ViewGroup就是一个控件组,它里面可以有很多个View,But ViewGroup也继承了View。关于ViewGroup我们可以这么理解,它里面可以有很多个View,这种关系就是一种View的树的结构。LinearLayout它既是一个View,也可以是一个ViewGroup。
我们图来表示一下,因为图是最直观的
再附上一张控件的结构层次表
](http://upload-images.jianshu.io/upload_images/3986342-c67678af1a40f908.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
1.2 View的位置参数
View的位置有四属性来决定,top left right bottom,top对应的是左上角的纵坐标,ldft对应的是左上角的横坐标,right对应的是右下角的横坐标,bottom对应的是右下角的纵坐标。(注意这些坐标都是相对与父控件的。)还是如此我们来花个图来解释一下吧
从上图,我们可以得到View的高是bottom-top View的宽是right-left,那么你们会问如何获取到这四个属性的值呢,其实只要通过getTop getLeft 就可以获取相应的值。现在我们再提四个参数,X,Y,translationX,translationiY。X,Y是View左上角的左上角的坐标,而translationX和translationY,则表示可偏移量,这几个参数相当于父控件的坐标,而且translationX和translationY的初始值都是0,同样View也为他们提供了Get和Set的方法。
这几个参数的关系式 X = left + translationX Y = top + translationY
需要注意的是View在平移的时候,top和left表示的是原始左上角的位置,其值并不会该表,此时发送改变的是X,Y,translationX,translationY.
1.3 MotionEvent 和 TouchSlop
1.MotionEvent
在手指接触屏幕后发生的一系列事件,典型的有以下几种
- ACTION_DOWN——手指刚接触屏幕的时候
- ACTION_MOVE——-手指在屏幕上移动的时候
- ACTION_UP——-手指离开屏幕的时候
正常情况下,一次手指接触屏幕会出发两种情况
1. 第一种,DOWN–>UP 当点击屏幕马上离开
2. 第二种,DOWN–>MOVE–>…–>MOVE–>UP 当点击屏幕并且在屏幕上移动在离开
3. 第三种,DOWN–>MOVE–>…–>MOVE 就是点击屏幕,并且移动,移动到屏幕外面
上述三种是典型的事件顺序,同时我们可以通过MotionEvent去获取点击时间发送的X,Y的坐标。系统提供了两组方法,getX\getY, getRawX\getRawY,其中第一组是用来返回当前View的左上角的x y坐标,第二组方法是用来返回手机屏幕左上角的x y坐标。那么我们还是看图说话吧
2 TouchSlop
TouchSlop就是系统所能识别的最小滑动距离,也就是说当用于滑动的距离小于该值则视为无效滑动。这是一个常量,不同的设备是不一样的。我们可以通过ViewConfiguration.get(getApplicationContext()).getScaledTouchSlop(); 来获取最小滑动距离。为了让大家更好的理解附上源码
可以看到最小识别的距离为8dp
1.4 VelocityTracker和Scroll
1 VelocityTracker
VelocityTracker通过跟踪一连串事件实时计算出当前的速度,通过它我们可以得数水平滑动和竖直滑动的速率,一般作用在onTouchEvent方法中,主要用到下面几个方法
addMovement(MotionEvent)函数将Motion event加入到VelocityTracker类实例中
getXVelocity() 或getXVelocity()获得横向和竖向的速率到速率时,computeCurrentVelocity(int)来初始化速率的单位 。
VelocityTracker.obtain();获得VelocityTracker类实例
话不多说直接上代码。
onTouchEvent(MotionEvent ev){ if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain();//获得VelocityTracker类实例 } mVelocityTracker.addMovement(ev);//将事件加入到VelocityTracker类实例中 //判断当ev事件是MotionEvent.ACTION_UP时:计算速率 // 1000 provides pixels per second velocityTracker.computeCurrentVelocity(1, (float)0.01); //设置maxVelocity值为0.1时,速率大于0.01时,显示的速率都是0.01,速率小于0.01时,显示正常 Log.i("test","velocityTraker"+velocityTracker.getXVelocity()); velocityTracker.computeCurrentVelocity(1000); //设置units的值为1000,意思为一秒时间内运动了多少个像素 Log.i("test","velocityTraker"+velocityTracker.getXVelocity()); }
2 Scroll
弹性滑动对象,用于实现View的弹性滑动。我们知道,当使用View的scrollTo/scrollBy方法来进行滑动时,其过程是瞬间完成,没有过渡效果的滑动用户体验不好。这个时候就需要使用Scroller来实现有过渡效果的滑动,大致实现过程后面会详细介绍,下面就附上实现代码。
Scroller scroller; scroller = new Scroller(context); //调用此方法滚动到目标位置 public void smoothScrollTo(int fx, int fy, boolean back) { int dx = fx; int dy = fy; smoothScrollBy(dx, dy); } //调用此方法设置滚动的相对偏移 public void smoothScrollBy(int dx, int dy) { //设置scroller的滚动偏移量 scroller.startScroll(scroller.getFinalX(), scroller.getFinalY(), dx, dy); invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果} } @Override public void computeScroll() { //先判断scroller滚动是否完成 if (scroller.computeScrollOffset()) { //这里调用View的scrollTo()完成实际的滚动 scrollTo(scroller.getCurrX(), scroller.getCurrY()); //必须调用该方法,否则不一定能看到滚动效果 postInvalidate(); } super.computeScroll(); }
2 View的滑动
在View的事件体系(1)中,我们已经了解到View的基本知识,这一节要来讲解很重要的东西就是View的滑动。在Android的设备上,滑动可以说以一种标配,不管是下拉刷新还是什么,他们的基础都是滑动。不管是任何酷炫的滑动效果,归根结底他们都是由不同的滑动和一些动画组成。所以我们还有必要去了解滑动的基础,接下来我们来了解三种实现滑动的方法。
1.View通过自生的scrollTo/scrollBy来实现滑动
2.通过动画来给View添加平移的效果来实现滑动
3.改变View的LayoutParams使得View重新布局来实现滑动。
2.1使用scrollTo/scrollBy
public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { postInvalidateOnAnimation(); } } } public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
从源码上可以看出,scrollBy实际上也是调用了scrollTo方法,它实现了基于当前位置的相对滑动,而scrollTo是实现了基于所传参数的绝对滑动。利用这两个方法我们就可以实现View的滑动,但是我们要明白滑动过程中View的两个内部属性的作用mScrollX mScrollY,这两个参数我们可以通过getScrollX getScrollY去获取。mScrollX的总值等于View内容原始左边缘到View内容现在左边缘的水平方向的距离,mScrollY的总值等于View内容原始上边缘到View内容现在上边缘的水平方向的距离。记住一句话上正下负右正左负,意思就是内容的上边缘在View的上边缘的上面,mScrollY为正,其他同理,给大家一个图便于理解
切记,再怎么滑动不能将View的位置进行改变,只能改变View内容的位置,比如TextView改变里面的文字
在 使用 getScrollY() 方法的时候,就是 getScrollY()的值 一直是 1.0
解决:通过查看 getScrollY() 方法 ,发现它有两个 返回值 一个 int , 一个 float , 后 将值 赋值给 int 类型后,就可以使用了;而直接 相加的为 float 类型;
2.2 使用动画
通过动画我们可以让View进行平移,而平移也是一种滑动。我们可以使用View动画也可以使用属性动画。
//View动画<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" android:duration="2000" android:startOffset="1000" android:fillAfter="true"> <scale android:fromXScale="0.0" android:toXScale="1.4" android:fromYScale="0.0" android:toYScale="1.4" android:pivotX="50" android:pivotY="50" android:duration="700" /> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" /></set> Animation animation = AnimationUtils.loadAnimation(this, R.anim.demo); tv1.startAnimation(animation);//属性动画 ObjectAnimator.ofFloat(tv1,"translationY",150).start(); ValueAnimator animator = ObjectAnimator.ofInt(tv1, "backgroundColor", 0xFFFF8080, 0xFF8080FF); animator.setDuration(3000); animator.setEvaluator(new ArgbEvaluator()); animator.setRepeatCount(5); animator.setRepeatMode(ValueAnimator.REVERSE); animator.start();
切记,我们对通过动画对View的移动其实是对View的影像的移动,若我们不把fillAfter设为true的话,移动完后又会回到起点,若为true则会保留不动。但我们也View设置一个点击事件的时候,就要区分动画的类型,若是View动画则点击移动后的View却触发不了点击事件,若是属性动画则点击移动后的View却触发点击事件。针对View动画的解决方案,我们需要在移动后的位置再建立一个通向的View。
2.3 改变布局参数
我们可以改变布局参数LayoutParams ,让我们想让一个Button向右移动100dp,那么我们只要设施其marginleft就可以了,还可以这这个Button设置一个宽度为0的View,改变其的宽度为,那个Button就会自动被挤到右边。
2.4各种滑动方式的对比
scrollTo/scrollBy,这种方法其实是View提供的原生的滑动方式,他可以实现View的滑动也不影响其内部的点击事件,缺点就是只能滑动View的内容
动画滑动,如果是android3.0以上的话可以采用属性动画,这种方法并没有什么缺点,如果是3.0一下的话就绝不能改变View本生的属性。实际上如果动画不需要响应用户的交互,那么这种滑动方式是比较合适的。但是动画有一个明显的有点,就是一些复杂的效果必须通过动画来实现。
改变布局的方式,除了使用起来麻烦以外就没有什么明显的缺点了,非常适合对象具有交互的View,因为这些View是要与用户交互,直接通过动画会有问题。
总结一下就是
scrollTo/scrollBy:操作简单,适合对View内容的滑动
动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果
改变布局参数:操作稍微复杂,适用于有交互的View
下面附上一个拖动的Demopublic class Move_textview extends TextView { String TAG = "move"; public Move_textview(Context context) { this(context, null); } private int mScaledTouchSlop;//可识别的最小滑动距离 // 分别记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; public Move_textview(Context context, AttributeSet attrs) { super(context, attrs); init(); } public Move_textview(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mScaledTouchSlop = ViewConfiguration.get(getContext()) .getScaledTouchSlop(); Log.d(TAG, "sts:" + mScaledTouchSlop); } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getRawX(); int y = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { break; } case MotionEvent.ACTION_MOVE: { int move_x = x - mLastX; int move_y = y - mLastY; int translationX = (int) ViewHelper.getTranslationX(this) + move_x; int translationY = (int)ViewHelper.getTranslationY(this) + move_y; ViewHelper.setTranslationX(this, translationX); ViewHelper.setTranslationY(this, translationY); } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return true; }}
3.弹性滑动
知道了View的滑动,但是这样的滑动有时候是比较生硬的,用户体验太差了,所以我们要去了解弹性滑动,就是将一次滑动分成若干个小滑动。主要是通过Scroller,handler #postDelaked,Thread#sleep
3.1Scroller的使用
我们先来看看Scroller的最基本的使用Scroller scroller = new Scroller(context);private void smoothScrollTo(int destX, int destY) {//自己写的方法 int scrollX = getScrollX();//View的内容的左边缘到View左边缘的距离 int deltaX = destX + scrollX;//加上要移动的距离后的位置 scroller.startScroll(scrollX, 0, deltaX, 0); invalidate();注解1}@Overridepublic void computeScroll() { if (scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), scroller.getCurrY()); postInvalidate(); }}
我们可以看到显示构造了一个Scroller对象,在调用它的startScroll方法,其实Scroller内部什么都没做就是用来保存几个参数
public void startScroll(int startX, int startY, int dx, int dy, int duration) { mMode = SCROLL_MODE; mFinished = false; mDuration = duration; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mStartX = startX; mStartY = startY; mFinalX = startX + dx; mFinalY = startY + dy; mDeltaX = dx; mDeltaY = dy; mDurationReciprocal = 1.0f / (float) mDuration;}
startX,startY是起始位置,dx dy是要滑动的距离,duration就是在规定的时间内滑动
那么Scroller到底是怎么进行弹性滑动的呢?注解1invalidate()
大致的流程是这样子的,invalidate方法会导致View去重绘,ondraw是绘制的方法,改方法又会去调用ComputeScroll方法,此时我们需要对ComputeScroll进行重写,在里面去判断弹性滑动是否结束,没结束就再获取Scroller当前的位置,在去进行第二次绘制,直至弹性滑动结束。
那我们来看看Scroller的computeScrolloffset方法
public boolean computeScrollOffset() { if (mFinished) { return false; } int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed < mDuration) { switch (mMode) { case SCROLL_MODE: final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal); mCurrX = mStartX + Math.round(x * mDeltaX); mCurrY = mStartY + Math.round(x * mDeltaY); break; case FLING_MODE: final float t = (float) timePassed / mDuration; final int index = (int) (NB_SAMPLES * t); float distanceCoef = 1.f; float velocityCoef = 0.f; if (index < NB_SAMPLES) { final float t_inf = (float) index / NB_SAMPLES; final float t_sup = (float) (index + 1) / NB_SAMPLES; final float d_inf = SPLINE_POSITION[index]; final float d_sup = SPLINE_POSITION[index + 1]; velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); distanceCoef = d_inf + (t - t_inf) * velocityCoef; } mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f; mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX)); // Pin to mMinX <= mCurrX <= mMaxX mCurrX = Math.min(mCurrX, mMaxX); mCurrX = Math.max(mCurrX, mMinX); mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY)); // Pin to mMinY <= mCurrY <= mMaxY mCurrY = Math.min(mCurrY, mMaxY); mCurrY = Math.max(mCurrY, mMinY); if (mCurrX == mFinalX && mCurrY == mFinalY) { mFinished = true; } break; } } else { mCurrX = mFinalX; mCurrY = mFinalY; mFinished = true; } return true;}
该方法就是根据流逝时间的百分比会算去scrollX和scrollY,当返回true的时候表示弹性滑动还没结束,false就表示弹性滑动结束
那么现在来总结一下,scroller本生是不能滑动的,它需要配合computeScroll来实现弹性滑动,它会不断的去重绘View,每次重绘是有时间间隔的,通过这个时间间隔和Scroller的computeScrolloffset方法来返回相应的位置,通过View自身的scrollTo和返回来的位置去移动View。这个思想很巧妙,竟然连计时器都没有用到。
注意:滑动的还是View的内容而不是View
3.2 通过动画
动画本来就是一种渐进的过程,因此通过它来实现的滑动天然就具有弹性效果,比如以下代码可以让一个Button实现一个宽度的变化动画。
private static class View_button { View view; public View_button(View view) { this.view = view; } public int getWidth() { return view.getLayoutParams().width; } public void setWidth(int w) { view.getLayoutParams().width = w; view.requestLayout(); } } ObjectAnimator.ofInt(new View_button(tv1),"width",500).setDuration(3000).start();
至于属性动画的详情,在后续中会详细介绍
3.3 使用延时策略
通过Handler里去改变控件的位置。
private Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_SCROLL_TO: { mCount++; if (mCount <= FRAME_COUNT) { float fraction = mCount / (float) FRAME_COUNT; int scrollX = (int) (fraction * 100); mButton1.scrollTo(scrollX, 0); mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO, DELAYED_TIME); } break; } default: break; } }; };
- 第三章 View事件体系(1)
- 第三章View的事件体系
- 第三章 View的事件体系
- 第三章 View事件体系(2)之事件分发
- Android开发艺术探索 读书笔记 第三章 View事件体系
- 《Android开发艺术探索》第三章View事件体系小结
- 第三章 View事件体系(3)之滑动冲突
- Android 开发艺术与探究 第三章 View的事件体系之View的基础知识
- Android View 事件体系1
- View的事件体系(1)-View基础知识
- View的事件体系 - Android开发艺术探索读书笔记(第三章)
- Android开发艺术探索——第三章View事件体系读书笔记
- 《Android 开发艺术探索》随手笔记——第三章View的事件体系
- Android 开发艺术探索读书笔记 第三章 View的事件体系
- Android艺术开发探索第三章——View的事件体系(上)
- Android艺术开发探索第三章————View的事件体系(下)
- 第三章View的事件体系(Android开发艺术探索)
- 《Android开发艺术探索》读书笔记-第三章 View的事件体系
- linux下的socket程式说明
- c语言中typedef的几种用法
- 鸡汤有毒:千万不要有这个项目离不开你的错觉
- 快速排序
- mysql 删除重复数据只留一条
- 第三章 View事件体系(1)
- 数数字(Digit Counting , ACM/ICPC Danang 2007, UVa1225)
- [22]Window PowerShell DSC学习系列---- 如何用PowerShell脚本查看DSC服务器的执行报表(Report)
- Unity 捕捉安卓虚拟键
- 动画资源
- 新建数据库失败,提示"template1" being accessed by other users
- QQ18年,解密8亿月活的QQ后台服务接口隔离技术
- gif透明色相关
- 梯度下降(Gradient Descent)小结