概述
目录
- 一.View的基础知识
- 1.什么是View
- 2.View的位置参数
- 3.MotionEvent
- 4. TouchSlop
- 5. VelocityTracker
- 6. GestureDetector
- 二.View的滑动
- 1. 使用scrollTo/scrollBy
- 1.1 getScrollX()、getScrollY()
- 1.2 scrollTo/scrollBy
- 1.3 scrollTo实例使用
- 2. 使用动画
- 3. 改变布局参数
- 4. 各种滑动方式对比
- 三.弹性滑动
- 1.使用Scroller
- 2.使用动画
- 3.使用延时策略
- 四.例子源码
一.View的基础知识
1.什么是View
View是一种界面层的控件的一种抽象,它代表了一个控件,是Android中所有控件的基类。
2.View的位置参数
3.MotionEvent
在我们触摸屏幕的过程中,可以分为三种情况,分别是按下、滑动、弹起。Android中为我们封装好了一个MotionEvent类,使得我们对屏幕的一系列操作事件都可以记录在这个MotionEvent里面。
- ACTION_DOWN —— 手指刚接触屏幕
- ACTION_MOVE —— 手指在屏幕上移动
- ACTION_UP —— 手指从屏幕上松开的一瞬间
通过MotionEvent对象我们可以得到点击事件发生的x和y坐标
- getX/getY 相对当前View左上角的x和y坐标
- getRawX/getRawY 相对手机屏幕左上角的x和y坐标
事件序列:由一个ACTION_DOWN事件,0个或者1个或者多个ACTION_MOVE事件,加上一个ACTION_UP事件组成的一个序列
4. TouchSlop
TouchSlop是系统所能识别出的被认为是滑动的最小距离,这是一个常量,和设备有关,在不同设备上这个值可能不同
ViewConfiguration这个类主要定义了UI中所使用到的标准常量,像超时、尺寸、距离,如果我们需要得到这些常量的数据,我们就可以通过这个类来获取,具体方法如下:
获取ViewConfiguration实例:
ViewConfiguration viewConfiguration = ViewConfiguration.get(Context);
常用的方法
// 获取touchSlop (系统 滑动距离的最小值,大于该值可以认为滑动)
int touchSlop = viewConfiguration.getScaledTouchSlop();
// 获得允许执行fling (抛)的最小速度值
int minimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
// 获得允许执行fling (抛)的最大速度值
int maximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
// Report if the device has a permanent menu key available to the user
// (报告设备是否有用户可找到的永久的菜单按键)
// 即判断设备是否有返回、主页、菜单键等实体按键(非虚拟按键)
boolean hasPermanentMenuKey = viewConfiguration.hasPermanentMenuKey();
5. VelocityTracker
速度追踪,用于追踪手指在滑动过程中的速度,包含水平和竖直方向的速度。
使用过程:
//VelocityTracker是Android系统内置的速度追踪类,首先调用它来追踪当前
//点击事件的速度,event一般是通过onTouchEvent函数传递的MotionEvent对象
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
//先调用computeCurrentVelocity函数,用于设定计算速度的时间间隔
velocityTracker.computeCurrentVelocity(1000);
//这里的速度指的是一段时间内手指划过的像素数,比如将时间间隔设置为1000ms,
//在1s内,手指在水平方向划过100像素,水平速度就是100.
// 速度的计算为(终端位置-起始位置)/间隔时间。
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
//不需要时,调用clear方法回收并重置内存
velocityTracker.clear();
velocityTracker.recycle();
6. GestureDetector
手势检测,用于辅助检测用户的单击,滑动,长按,双击等行为,使用系统的GestureDector来监听这些事件
GestureDetector内部的Listener接口:
监听器 | 简介 |
---|---|
OnGestureListener | 手势检测,主要有:按下(Down)、快速滑动(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress)和单击抬起(SingleTapUp) |
OnDoubleTapListener | 双击检测,主要有三个回调类型:双击(DoubleTap)、单击确认(SingleTapConfirmed)和双击事件回调(DoubleTapEvent) |
OnContextClickListener | 这是Android6.0(23)才添加的,用于检测外部设备上按钮是否按下,一般情况下可以忽略 |
SimpleOnGestureListener | 该类是GestureDetector提供给我们的一个更方便非响应不同手势的类,这个类实现了上述三个接口(但是所有方法都是空的),该类是static类,也就是说它是一个外部类。可以在外部继承这个类,重写里面的手势处理方法。 |
使用:
- 实现OnGestureListener/OnGestureListener/SimpleOnGestureListener接口
- 实例化GestureDetectorCompat类
- 接管目标View的OnTouchEvent方法
GestureDetector.OnGestureListener listener = new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
//手指按下的瞬间
return false;
}
@Override
public void onShowPress(MotionEvent e) {
//手指触摸屏幕,并且尚未松开或拖动。与onDown的区别是,onShowPress强调没用松开和没有拖动
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
//手指离开屏幕(单击)
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//手指按下并拖动,当前正在拖动
return false;
}
@Override
public void onLongPress(MotionEvent e) {
//手指长按事件
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//手指快速滑动
return false;
}
};
mGestureDetector = new GestureDetector(this,listener);
//防止长按后无法拖动的问题
mGestureDetector.setIsLongpressEnabled(false);
@Override
public boolean onTouchEvent(MotionEvent event) {
//既然要让GestureDetector来识别各种动作事件,那么就得让GestureDetector来接管事件管理,即在onTouchEvent里面只写入如下代码
return mGestureDetector.onTouchEvent(event);
}
二.View的滑动
View的滑动的实现有3种方法:
- 使用scrollTo/scrollBy
- 使用动画
- 改变布局参数
1. 使用scrollTo/scrollBy
scrollTo、scrollBy方法是View中的,因此任何的View都可以通过这两种方法进行移动。首先要明白的是, scrollTo、scrollBy滑动的是View中的内容(而且还是整体滑动),而不是View本身。我们的滑动控件如SrollView可以限定宽、高大小,以及在布局中的位置,但是滑动控件中的内容(或者里面的childView)可以是无限长、宽的,我们调用View的scrollTo、scrollBy方法,相当于是移动滑动控件中的画布Canvas,然后进行重绘
1.1 getScrollX()、getScrollY()
getScrollX()、getScrollY()得到的是偏移量,是相对自己初始位置的滑动偏移距离,只有当有scroll事件发生时,这两个方法才能有值,否则getScrollX()、getScrollY()都是初始时的值0,而不管你这个滑动控件在哪里。所谓自己初始位置是指,控件在刚开始显示时、没有滑动前的位置。以getScrollX()为例,其源码如下:
public final int getScrollX() {
return mScrollX;
}
可以看到getScrollX()直接返回的就是mScrollX,代表水平方向上的偏移量,getScrollY()也类似。偏移量mScrollX的正、负代表着,滑动控件中的 内容相对于 初始位置在水平方向上偏移情况,mScrollX为正代表着当前内容相对于初始位置向左偏移了 mScrollX的距离,mScrollX为负表示当前内容相对于初始位置向右偏移了mScrollX的距离。 这里的坐标系和我们平常的认知正好相反。
1.2 scrollTo/scrollBy
scrollTo(int x,int y)移动的是View中的内容,而滑动控件中的内容都是整体移动的,scrollTo(int x,int y)中的参数表示View中的内容要相对于内容初始位置移动x和y的距离,即将内容移动到距离内容初始位置x和y的位置。正如前面所说,在处理偏移、滑动问题时坐标系和平常认知的坐标系是相反的。以一个例子说明scrollTo():
(1)调用scrollTo(100,0)表示将View中的内容移动到距离内容初始显示位置的x=100,y=0的地方,效果如下图:
(2)调用scrollTo(0,100)效果如下图:
源码展示:
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();
}
}
}
scrollTo是相对于初始位置来进行移动的,而scrollBy(int x ,int y)则是相对于上一次移动的距离来进行本次移动。scrollBy其实还是依赖于scrollTo的,如下源码:
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
1.3 scrollTo实例使用
一个ViewGroup的实现类,实现手指水平滑动功能,这里只展示onTouchEvent里面的关键代码,完整代码后面给出
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX(); //相对当前View左上角的x坐标
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mLastX = x; //记录开始滑动时当前View左上角的x坐标
break;
case MotionEvent.ACTION_MOVE:
int dx = mLastX - x; //本次手势滑动了多大距离
int oldScrollX = getScrollX(); //原来的偏移量
int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量
if(preScrollX > (getChildCount() - 1) * getWidth()){
preScrollX = (getChildCount() - 1) * getWidth();
}
if(preScrollX < 0){
preScrollX = 0;
}
// scrollTo移动,注意只移动水平方向
scrollTo(preScrollX,getScrollY());
mLastX = x;
break;
}
return true;
}
2. 使用动画
通过使用动画,我们也可以实现一个View的平移,主要也是操作View的translationX和translationY,既可以补见动画,也可以采取属性动画。
关于动画:Android三种动画详解
普通动画
//layout下anim包中新建translate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:zAdjustment="normal">
<translate
android:duration="100"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="100"
android:toYDelta="100" />
</set>
属性动画
ObjectAnimator.ofFloat(layout,"translationX",0,100).setDuration(1000).start();
注意:
- View动画是对View的影像进行操作的。也就是说View动画并不能真正的改变View的位置。
- 属性动画是真正改变View的位置,但它是从Android3.0开始推出的。
3. 改变布局参数
改变布局参数,即改变LayoutParams,比如:我们想把一个Button向右平移100px,我们只需将这个Button的LayoutParams里的marginLeft参数的值增加100px即可
MarginLayoutParams params = (MarginLayoutparamsmButton1.getLayoutParams();
params.width + = 100;
params.leftMargin + = 100;
mButton1.requestLayout();
//或者 mButton1.setLayoutParams(params);
还有一种做法:在将要移动的View前面,设置一个空的,默认宽度为0的View,若想要平均移动View,只需要设置空View的宽度即可
4. 各种滑动方式对比
三.弹性滑动
实现View的弹性滑动,即渐进式滑动。实现的方法很多,但都有一个共同的思想,将一次大的滑动分成若干次小的滑动,并在一个时间段中完成,下面就是常见的实现滑动的方法。
1.使用Scroller
两个重要的方法
方法名 | 解释 |
---|---|
startScroll(int startX, int startY, int dx, int dy, int duration) | 开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处。 |
computeScrollOffset() | 滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中。 |
部分源码解读:
public class Scroller {
private int mStartX;//水平方向,滑动时的起点偏移坐标
private int mStartY;//垂直方向,滑动时的起点偏移坐标
private int mFinalX;//滑动完成后的偏移坐标,水平方向
private int mFinalY;//滑动完成后的偏移坐标,垂直方向
private int mCurrX;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,水平方向
private int mCurrY;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,垂直方向
private int mDuration; //本次滑动的动画时间
private float mDeltaX;//滑动过程中,在达到mFinalX前还需要滑动的距离,水平方向
private float mDeltaY;//滑动过程中,在达到mFinalX前还需要滑动的距离,垂直方向
public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
}
/**
* 开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处
*/
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;
}
/**
* 滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中
* @return
*/
public boolean computeScrollOffset() {
if (mFinished) {//已经完成了本次动画控制,直接返回为false
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);//计算出当前的滑动偏移位置,x轴
mCurrY = mStartY + Math.round(x * mDeltaY);//计算出当前的滑动偏移位置,y轴
break;
...
}
}else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
...
}
Scroller类中最重要的两个方法就是startScroll()和computeScrollOffset(),但是Scroller类只是一个滑动计算辅助类,它的 startScroll()和computeScrollOffset()方法中也只是对一些轨迹参数进行设置和计算,真正需要进行滑动还是得通过View的scrollTo()、scrollBy()方法。为此,View中提供了computeScroll()方法来控制这个滑动流程。computeScroll()方法会在绘制子视图的时候进行调用。其源码如下:
/**
- Called by a parent to request that a child update its values for mScrollX
- and mScrollY if necessary. This will typically be done if the child is
- animating a scroll using a {@link android.widget.Scroller Scroller}
- object.
- 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
*/
public void computeScroll() { //空方法 ,自定义滑动功能的ViewGroup必须实现方法体
}
因此Scroller类的基本使用流程可以总结如下:
- 首先通过Scroller类的startScroll()开始一个滑动动画控制,里面进行了一些轨迹参数的设置和计算;
- 在调用 startScroll()的后面调用invalidate();引起视图的重绘操作,从而触发ViewGroup中的computeScroll()被调用;
- 在computeScroll()方法中,先调用Scroller类中的computeScrollOffset()方法,里面根据当前消耗时间进行轨迹坐标的计算,然后取得计算出的当前滑动的偏移坐标,调用View的scrollTo()方法进行滑动控制,最后也需要调用invalidate();进行重绘。
如下的一个简单代码示例:
@Override
public boolean onTouchEvent(MotionEvent ev) {
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
int x = (int) ev.getX();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
if(!mScroller.isFinished()){
mScroller.abortAnimation();
}
mLastX = x;
break;
case MotionEvent.ACTION_MOVE:
int dx = mLastX - x;
int oldScrollX = getScrollX();//原来的偏移量
int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量
if(preScrollX > (getChildCount() - 1) * getWidth()){
preScrollX = (getChildCount() - 1) * getWidth();
}
if(preScrollX < 0){
preScrollX = 0;
}
//开始滑动动画
mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,0);//第一步
//注意,一定要进行invalidate刷新界面,触发computeScroll()方法,因为单纯的startScroll()是属于Scroller的,只是一个辅助类,并不会触发界面的绘制
invalidate();
mLastX = x;
break;
}
return true;
}
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){//第二步
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//第三步
invalidate();
}
}
2.使用动画
动画本身就是一种渐进的过程,因此通过它来实现太天然就具备弹性效果。比如,下面的代码就可以让一个VIew在100ms向右移动100像素
ObjectAnimator.ofFloat(layout,"translationX",0,100).setDuration(100).start();
3.使用延时策略
通过发送一系列延时消息从而达到一种渐进式的效果,具体说可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。对于postDelayed方法来说,我们可以通过它来延时发送一个消息,然后在消息中来进行View的滑动,如果接连不断地发送这种延时消息,那么就可以实现弹性滑动的效果,对于sleep方法来说,通过在while循环中不断地滑动View和sleep,就可以实现弹性滑动的效果。
以Handled为例,下面的代码将VIew的内容向左移动100像素。
private static final int MESSAGE_SCROLL_TO = 1;
private static final int FRAME_COUNT = 30;
private static final int DELAYED_TIME = 33;
private int mCount;
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);
Button mButton;
mButton.scrollTo(scrollX,0);
mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO,DELAYED_TIME);
}
break;
}
default:
break;
}
}
};
四.例子源码
ViewText:关于上面滑动的demo
最后
以上就是含糊鸭子为你收集整理的Android View(一)——View的基础知识的全部内容,希望文章能够帮你解决Android View(一)——View的基础知识所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复