我是靠谱客的博主 含糊鸭子,这篇文章主要介绍Android View(一)——View的基础知识,现在分享给大家,希望可以做个参考。

目录

    • 一.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实例:

复制代码
1
2
ViewConfiguration viewConfiguration = ViewConfiguration.get(Context);

常用的方法

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
// 获取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

速度追踪,用于追踪手指在滑动过程中的速度,包含水平和竖直方向的速度。
使用过程:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//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方法
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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);
复制代码
1
2
3
4
5
6
@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()为例,其源码如下:

复制代码
1
2
3
4
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)效果如下图:
在这里插入图片描述
源码展示:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的,如下源码:

复制代码
1
2
3
4
public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }

1.3 scrollTo实例使用

一个ViewGroup的实现类,实现手指水平滑动功能,这里只展示onTouchEvent里面的关键代码,完整代码后面给出

在这里插入图片描述

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@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三种动画详解
 
普通动画

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//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>

属性动画

复制代码
1
2
ObjectAnimator.ofFloat(layout,"translationX",0,100).setDuration(1000).start();

注意:

  • View动画是对View的影像进行操作的。也就是说View动画并不能真正的改变View的位置。
  • 属性动画是真正改变View的位置,但它是从Android3.0开始推出的。

3. 改变布局参数

改变布局参数,即改变LayoutParams,比如:我们想把一个Button向右平移100px,我们只需将这个Button的LayoutParams里的marginLeft参数的值增加100px即可

复制代码
1
2
3
4
5
6
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值中。

部分源码解读:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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()方法会在绘制子视图的时候进行调用。其源码如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
/** - 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();进行重绘。

如下的一个简单代码示例:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@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像素

复制代码
1
2
ObjectAnimator.ofFloat(layout,"translationX",0,100).setDuration(100).start();

3.使用延时策略

通过发送一系列延时消息从而达到一种渐进式的效果,具体说可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。对于postDelayed方法来说,我们可以通过它来延时发送一个消息,然后在消息中来进行View的滑动,如果接连不断地发送这种延时消息,那么就可以实现弹性滑动的效果,对于sleep方法来说,通过在while循环中不断地滑动View和sleep,就可以实现弹性滑动的效果。
以Handled为例,下面的代码将VIew的内容向左移动100像素。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(89)

评论列表共有 0 条评论

立即
投稿
返回
顶部