在 Android 中想要实现实现滑动有很多方法,这篇博客将提供一些实现滑动的思路,希望可以帮助到有需要的人。
一、Android 坐标体系
在讲解滑动之前,我们有必要简单提一下 Android 的坐标体系,因为滑动的实质就是坐标的不断改变,所以我们先来了解一下 Android 坐标系和视图坐标系两个概念。直接放上两张图片吧,一目了然。
从上面的两张图可以看出,Android 坐标系的坐标原点位于屏幕的左上角,而视图坐标系的原点位于父视图的左上角,既然提供了两种不同的坐标系,那么我们如何来获取坐标呢,Android 已经给我们提供了一些方法用于获取这些坐标,看下面的图便一目了然。
二、layout 方法
在 View 进行绘制时,是调用 onLayout() 方法来确定 View 的位置的,同样我们也可以调用 layout() 方法来传入我们滑动后的坐标便可以实现 View 的滑动,当然坐标的获取我们可以在触控事件中进行获取,下面我们做一个 View 随手指进行滑动的小例子来进行说明。
- public class DragView extends View {
- private int mLastX;
- private int mLastY;
- public DragView(Context context) {
- this(context, null);
- }
- public DragView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }@Override public boolean onTouchEvent(MotionEvent event) {
- int x = (int) event.getX();
- int y = (int) event.getY();
- int lastX = 0,
- lastY = 0;
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mLastX = x;
- mLastY = y;
- break;
- case MotionEvent.ACTION_MOVE:
- int offsetX = x - mLastX;
- int offsetY = y - mLastY;
- layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
- break;
- }
- return true;
- }
- }
上面我们在触控事件中获取到获取到手指按下时的坐标 (lastX, lastY),然后在手指移动时不断计算 X 和 Y 方向上的偏移量,然后再调用 layout() 方法来改变 View 的位置从而实现滑动。当然上面我们是通过 getX()和 getY()来获取视图坐标来进行修改,我们也可以通过 getRawX()和 getRawY()来获取绝对坐标来实现上面的效果。代码如下:
- private int mLastX;
- private int mLastY;@Overridepublic boolean onTouchEvent(MotionEvent event) {
- int x = (int) event.getRawX();
- int y = (int) event.getRawY();
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mLastX = x;
- mLastY = y;
- break;
- case MotionEvent.ACTION_MOVE:
- int offsetX = x - mLastX;
- int offsetY = y - mLastY;
- layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY); //重新设置初始坐标 mLastX = x; mLastY = y; break; } return true;}
上面一定要注意,我们在改变完 View 的位置后必须调用设置初始坐标,这样才能准确获取偏移量。
三、offsetLeftAndRight 和 offsetTopAndBottom
这一种方法和上一种方法大部分步骤都是相同的,只是在移动 View 上有所差别,代码如下:
- offsetLeftAndRight(offsetX);
- offsetTopAndBottom(offsetY);
上面的这种方法只是多了一层封装,可以实现比上面实现同样的效果。
四、设置 LayoutParams
LayoutParams 可以通过改变的布局参数,我们可以通过下面的代码实现上面同样的效果。
- LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
- layoutParams.leftMargin = getLeft() + offsetX;
- layoutParams.topMargin = getTop() + offsetY;
- setLayoutParams(layoutParams);
注意: 我们的 LayoutParams 可以通过 getLayoutParams() 方法来获取,但是要注意,如果 View 的父布局是 LinearLayout,那么我们的 LayoutParams 就是 LinearLayout.LayoutParams,如果 View 的父布局是 RelativeLayout, 则我们的 LayoutParams 就是 RelativeLayout.LayoutParams。当然我们还有一种简单的方法,不用再管父布局的布局方式。代码如下:
- ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
- marginLayoutParams.leftMargin = getLeft() + offsetX;
- marginLayoutParams.topMargin = getTop() + offsetY;
- setLayoutParams(marginLayoutParams);
上面的这种方法不用管父布局的类型,使用起来更加方便。
五、scrollTo 和 scrollBy 方法
关于这两个方法我们需要仔细说一下其中的一些注意事项
1 . scrollTo 的参数是具体的一个坐标点 (x, y), 而 scrollBy 的参数是在 x, y 方向上的坐标偏移
2 . scrollTo 和 scrollBy 移动的是 View 的内容。这一点很重要!!!!
如果我们对 ViewGroup 使用 scrollTo 和 scrollBy 则移动的是内部的所有子 View, 如果对 TextView 使用 scrollTo 和 scrollBy 则移动的是其中额文本。
3 . 视图移动还有一个不太好理解的地方在于坐标,我们下面结合图片来说明一下:
我们可以这样理解,我们的手机屏幕作为一个盖板,在手机屏幕下面是一个巨大的画布,我们的手机屏幕这个盖板是透明的,导致只有和手机屏幕重合的画布部分才会被我们看到,我们调用 scrollTo 和 scrollBy 也可以理解为是在移动手机上面的盖板。如图中所示,按钮在 ViewGroup 中的坐标是 (20, 10), 当我们调用 scrollBy(20, 10) 之后,就相当于移动了屏幕上的盖板,然后我们看到的按钮就到了 ViewGroup 的左上角。这样如果我们想让按钮在水平和竖直方向上各移动 20 和 10 个单位,我们就必须调用 scrollBy(-20, -10)
经过了上面的知识准备,我们这里也使用 scrollBy 来实现前面实现的那个 View 随手指移动的小例子:
- ((View)getParent()).scrollBy(-offsetX, -offsetY);
六、使用 Scroller
Scroller 也是滑动中很重要的一个角色, 进过前面的 scrollTo 和 scrollBy 大家也会发现,它们的移动时瞬间完成的,滑动显得十分突兀,Google 为了改善用户体验,便给出了 Scroller,它可以实现平滑的移动,从而使滑动过程更加真实,用户体验更好,下面我们先简单说说 Scroller 的实现原理。
Scroller 的实现方式类似于 scrollTo 和 scrollBy,scrollTo 和 scrollBy 的移动都是从一个坐标点瞬间移动到另一个左边点,而 Scroller 则是将移动的这段距离切分成好几段的微小的位移,然后每一段调用 scrollTo 来不断移动这些微小的位移,由于人眼的视觉暂留效果,就会给人平滑移动的视觉效果。
下面我们在上一步的基础上增加一个小功能,第一部分还是 View 随手指移动,但是当我们松开手指时,让 View 自己平滑移动到最初始的位置 (屏幕左上角), 下面我们就来一步步介绍 Scroller 的用法
1 . 声明 Scroller 变量, 并在构造方法中进行初始化
2 . 在触控事件的 ACTION_UP(手指抬起) 事件中传入开始滑动的坐标和需要滑动的距离并触发 Scroller 的滑动事件
3 . 重写 computeScroll(), 实现真正的滑动
下面是完整的代码示例:
- public class DragView extends View {
- private int mLastX;
- private int mLastY; //声明Scroller变量 private Scroller mScroller; public DragView(Context context) { this(context, null); } public DragView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //在构造方法中初始化Scroller变量 mScroller = new Scroller(context); } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getRawX(); int y = (int) event.getRawY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: mLastX = x; mLastY = y; break; case MotionEvent.ACTION_MOVE: int offsetX = x - mLastX; int offsetY = y - mLastY; //实现View跟随手指移动的效果 ((View)getParent()).scrollBy(-offsetX, -offsetY); //重新设置初始坐标 mLastX = x; mLastY = y; break; case MotionEvent.ACTION_UP: //当手指抬起时执行滑动过程 View view = (View) getParent(); mScroller.startScroll(view.getScrollX(), view.getScrollY(), view.getScrollX(), view.getScrollY(), 5000); //调用重绘来间接调用computeScroll()方法 invalidate(); break; } return true; } @Override public void computeScroll() { super.computeScroll(); //判断滑动过程是否完成 if (mScroller.computeScrollOffset()){ ((View)getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //通过重绘来不断调用computeScroll()方法 invalidate(); } }}
上面的代码 View 随手指移动的代码部分是与前面相同的,我们只说说 Scroller 的部分以及一些注意事项
1 . startScroll() 方法各参数的意义, 我们可以看看下面的源码:
- /** * Start scrolling by providing a starting point, the distance to travel, * and the duration of the scroll. * * @param startX Starting horizontal scroll offset in pixels. Positive * numbers will scroll the content to the left. * @param startY Starting vertical scroll offset in pixels. Positive numbers * will scroll the content up. * @param dx Horizontal distance to travel. Positive numbers will scroll the * content to the left. * @param dy Vertical distance to travel. Positive numbers will scroll the * content up. * @param duration Duration of the scroll in milliseconds. */
- public void startScroll(int startX, int startY, int dx, int dy, int duration)
可以看出 startX 和 startY 参数就是开始滚动的 (x, y) 坐标,那么我们就可以通过 ViewGroup(子 View 的父视图)的 getScrollX()和 getScrollY()来获取, 这里一定要注意,我们在滑动时的 content 就是子 View,所以我们通过子 View 的父视图 (ViewGroup) 的 getScrollX()和 getScrollY()获取到的就是子 View 在 X 和 Y 方向上滑动的距离,即就是我们需要的当我们手指抬起时子 View 的 (x, y) 坐标。而如果我们对子 View 调用 getScrollX()和 getScrollY()方法,则获得的是子 View 内部的视图的滑动距离及坐标。
dx 和 dy 分别是在 X 和 Y 方向上的偏移量,而且注释中说了,如果我们传入的 dx 和 dy 的值是正值,那么将会向上向左移动这个 content(其实就是我们这里的 View),即我们就可以让子 View 回到左上角,这里我们还是可以借助于上一小节中提到的视图移动的概念,我们想让子 View 向坐上方移动,其实就是想让覆盖在上面的盖板向右下角移动,我们可以将 dx 和 dy 理解为父视图 (覆盖在上面的盖板) 的偏移量。
假设我们刚开始是让子 View 随手指向右下方移动,那么相当于覆盖在上面的盖板是向左上方移动,所以我们通过 getScrollX()和 getScrollY()获得的值是负值,我们现在松开手指想让子 View 向左上方移动 (即回到屏幕左上角),那么就相当于盖板向右下角移动,所以我们的 dx 和 dy 的值必须是 - getScrollX() 和 - getScrollY(),此时的两个值都是正值。
2 . 由于我们的 computeScroll() 方法不会主动调用,但是我们又需要它不断调用从而不断进行微小移动从而实现平滑的滑动,所以我们可以通过下面的方法。
这三个按照以下顺序进行调用 invalidate()—>onDraw()—>computeScroll(),所以我们可以可以在 ACTION_UP 中调用完 startScroll() 方法后调用 invalidate() 方法, 然后在 computeScroll() 方法中判断滑动是否结束, 如果没结束,则通过 getCurrX() 和 getCurrY() 来获得当前需要移动的微小的位移的坐标点,然后传入 scrollTo() 方法中,这时候子 View 还只是移动了一小段距离,然后我们再次调用 invalidate() 方法,然后接着调用 onDraw() 方法,然后再次进入 computeScroll() 中再次让子 View 移动一小段距离,直到滑动结束, computeScrollOffset() 返回 false,则这个循环调用的过程结束,从而完成平滑移动的过程。
七、属性动画
属性动画一样可以实现 View 的滑动,但是由于属性动画涉及到的知识点也是众多,这里不再展开来写,只是提供一个思路,后续后专门写一篇博客来说。
八、ViewDragHelper
ViewDragHelper 可以帮助我们实现各种滑动需求,但是它的使用也相对较复杂,所以准备专门写一篇博客来介绍他,这里只是给出一个概念
就爱阅读 www.92to.com 网友整理上传, 为您提供最全的知识大全, 期待您的分享,转载请注明出处。
来源: