Android Scroll分析

简介: Android Scroll分析

概述


相对于Android2.x版本中常见的长按、点击操作,滑动的方式具有更友好的用户体验性。因此从4.x的版本开始,滑动操作大量出现在Android系统中。


我们在这里主要阐述两个问题

  1. 发生滑动的效果的原因
  2. 如何处理、实现滑动效果


滑动效果分析


滑动一个View,本质上就是移动一个View。


改变其当前所处的位置,它的原理和动画效果的实现非常相似,都是通过不断的改变View的坐标来实现这一个效果。


所以要实现View的滑动,必须要监听用户的触摸事件,并根据事件传入的坐标,动态且不断的改变View的坐标,从而实现View跟随用户触摸的滑动而滑动。


在此之前,我们需要先了解下Android中的窗口坐标体系和屏幕的触控事件MotionEvent。


Android坐标系


所谓滑动,正是相对于参考系的运动。


在Android中,将屏幕最左上角的顶点作为Android坐标系的原点,从这个点向右是X轴的正方向,从这个点向下是Y轴的正方向。


20160406210255965.jpg


系统提供了getLocationOnScreen(int location[])这样的方法来获取Android坐标系中点的位置,即该视图左上角在Android坐标系中的坐标。


另外在触控事件中使用 getRawX(),getRawY()方法所获得的坐标同样是Android坐标系中的坐标。


视图坐标系


Android还有一个视图坐标系,它描述的是子视图在父视图中的位置关系。


和上面的Android坐标系相辅相成。


和Android坐标系类似,视图坐标系同样是以原点方向向右为X轴正方向,以原点向下为Y轴正方向,只是这个原点不再是Android坐标系中屏幕的左上角,而是父视图左上角为坐标原点。


20160406211442579.png


在触摸事件中,通过getX()和getY()所获得的坐标就是视图坐标系中的坐标。


触控事件-MotionEvent


触控事件MotionEvent在用户交互中,占据着举足轻重的位置。


首先我们来看下MotionEvent中封装的一些常用的事件变量,它定义了触控事件的不同的类型。

// 单点触摸按下动作
public static final int ACTION_DOWN = 0 ;
// 单点触摸离开动作
public static final int ACTION_UP = 1 ;
// 触摸点移动动作
public static final int ACTION_MOVE = 2 ;
// 触摸动作取消
public static final int ACTION_CANCEL = 3 ;
// 触摸动作超出边界
public static final int ACTION_OUTSIDE = 4 ;
// 多点触摸按下动作
public static final int ACTION_POINTER_DOWN = 5 ;
// 多点离开动作
public  static final int  ACTION_POINTER_UP = 6 ;



通常情况下,我们会在onTouchEvent(MotionEvnet event)方法中通过event.getAction()方法来获取触控事件的类型,并使用switch-case方法来进行筛选,这个代码的模式基本固定,如下


@Override
public boolean onTouchEvent(MotionEvent envnt){
    // 获取当前输入点的X、Y坐标(视图坐标)
    int x = (int)event.getX();
    int y = (int)event.getY();
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            //处理输入的按下事件
            break;
        case MotionEvent.ACTION_MOVE:
            // 处理输入的移动事件
            break;
        case MotionEvent.ACTION_UP:
            // 处理输入的离开事件
            break;
        }
    return true ;
}



在不涉及多点操作的情况下,通常可以使用以上代码来完成触控事件的监听,上述仅仅是一个代码模板~

在Android中提供了很多获取坐标值,相对举例的方法,我们来梳理一下。


20160406214617655.jpg


View 提供的获取坐标的方法

getTop():获取到的是View自身的顶边到其父布局顶边的距离

getLeft():获取到的是View自身的左边到其父布局左边的距离

getRight():获取到的是View自身的右边到其父布局左边的距离

getBottom():获取到的是View自身的底边到其父布局顶边的距离


MotionEvent 提供的方法

getX():获取点击事件距离控件左边的距离,即视图坐标

getY():获取点击事件距离控件顶边的距离,即视图坐标

getRawX():获取点击事件距离整个屏幕左边的距离,即绝对坐标

getRawY():获取点击事件距离整个屏幕顶边的距离,即绝对坐标


实现滑动的七种方法


不管使用何种方法,其实现的基本思路是一致的:当触摸View时,系统记下当前触摸点坐标,当手指移动时,系统记下移动后的触摸点坐标,从而获取到相对于前一次坐标点的偏移量,并通过偏移量来修改View的坐标,这样不断地重复,从而实现滑动的过程。


下面我们通过例子来看看Android是如何实现滑动效果的。


首先我们自定义一个View,置于布局文件中,实现一个简单的布局。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 <com.turing.base.android_hero.chapter5_Scroll.DragView
        android:layout_width="100dp"
        android:layout_height="100dp"/>
</RelativeLayout>


layout方法


概述


在View绘制时,会调用onLayout()方法来设置显示的位置。

同样,可以通过修改View的 left top right bottom四个属性来控制View的坐标。

在每次回调onTouchEvent方法的时候,我们都来获取一下触摸点的坐标。


Code


自定义DragView,重写onTouchEvent方法

package com.turing.base.android_hero.chapter5_Scroll;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
 * MyApp
 *
 * @author Mr.Yang on 2016-04-09  21:11.
 * @version 1.0
 *          自定义View
 */
public class DragView extends View {
    // 定义上次触摸的位置
    private int lastX;
    private int lastY;
    /**
     * 构造函数中调用 initViewColor方法
     *
     * @param context
     */
    public DragView(Context context) {
        super(context);
        initViewBackGroundColor();
    }
    public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initViewBackGroundColor();
    }
    public DragView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initViewBackGroundColor();
    }
    public void initViewBackGroundColor() {
        // 给View设置背景颜色,便于观察
        setBackgroundColor(Color.BLUE);
    }
    /**
     * 重写 onTouchEvent方法
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int rawx = (int) event.getRawX();
        int rawy = (int) event.getRawY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 按下的时候 记录触摸点坐标  scrollByXY
//                lastX = x;
//                lastY = y ;
                lastX = rawx;
                lastY = rawy;
                break;
            case MotionEvent.ACTION_MOVE:
                // scrollByXY(x,y);
                scrollyByRawXY(rawx, rawy);
                break;
        }
        return true;
    }
       /**
     * 使用Android坐标系 绝对坐标来计算偏移量,并移动View
     * 使用绝对坐标系,在每次执行完ACTION_MOVE的逻辑后一定要重新设置初始坐标,
     * 这样才能准确的获取到偏移量
     * @param rawx
     * @param rawy
     */
    public void scrollyByRawXY(int rawx, int rawy) {
        // 计算偏移量
        int offsetX = rawx - lastX;
        int offsetY = rawy - lastY;
        // 增加偏移量
        layout(getLeft() + offsetX,
                getTop() + offsetY,
                getRight() + offsetX,
                getBottom() + offsetY);
        // 重新设置初坐标
        lastX = rawx;
        lastY = rawy;
    }
    /**
     * 使用视图坐标系 计算偏移量,并移动View
     */
    public void scrollByXY(int x, int y) {
        // 计算偏移量
        int offsetX = x - lastX;
        int offsetY = y - lastY;
        // 在当前left right  top  bottom的基础上增加偏移量
        layout(getLeft() + offsetX,
                getTop() + offsetY,
                getRight() + offsetX,
                getBottom() + offsetY);
    }
}

Scroll_Layout

package com.turing.base.android_hero.chapter5_Scroll;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.turing.base.R;
public class Scroll_Layout extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scroll__layout);
    }
}


效果图


20160409225246212.gif

offsetLeftAndRight 和 offsetTopAndBottom


这个方法相当于系统提供了一个对左右和上下移动的API的封装。

当计算出偏移量之后,只需要使用如下代码完成View的重新布局,效果和使用layout方法一样

// 同时对left和right进行偏移
offsetLeftAndRigth(offsetX);
// 同时对top和bottom进行偏移
offsetTopAndBottom(offsetY);


效果同layout 就不贴代码和运行效果图了。


LayoutParams

概述


LayoutParams保存了一个View的布局参数。因此可以通过改变LayoutParms来动态的修改一个 布局的位置参数,从而达到改变View位置的效果。


我们可以通过getLayoutParams()来获取一个View的LayoutParams. 当然了计算偏移量和Layout方法中计算offset也是一样的,当获取到偏移量之后,就可以通过setLayoutParams来改变LayoutParams.


代码如下:

 // 获取偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                setLayoutParams(layoutParams);

注意事项:


通过getLayoutParams获取LayoutParams时,需要根据View在父布局中的类型来设置不同的类型,比如这里我们把View放到了RelativeLayout中,那么就是RelativeLayout.LayoutParams ,同样的道理 如果放到了LinearLayout中,则为LinearLayout.LayoutParams

通过getLayoutParams的方式获取布局参数,前提是必须要有一个父布局,否则系统无法获取。


Code


关键自定义类

package com.turing.base.android_hero.chapter5_Scroll;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
/**
 * MyApp
 *
 * @author Mr.Yang on 2016-04-10  10:23.
 * @version 1.0
 * @desc
 */
public class DragView_LayoutParams extends View {
    private int lastX, lastY;
    public DragView_LayoutParams(Context context) {
        super(context);
        initViewBackgroudCoclor();
    }
    public DragView_LayoutParams(Context context, AttributeSet attrs) {
        super(context, attrs);
        initViewBackgroudCoclor();
    }
    public DragView_LayoutParams(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initViewBackgroudCoclor();
    }
    private void initViewBackgroudCoclor() {
        // 给View设置背景颜色,便于观察
        setBackgroundColor(Color.BLUE);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 记录当前的触摸坐标
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                // 获取偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                setLayoutParams(layoutParams);
                break;
            default:
                break;
        }
        return true;
    }
}


效果图

效果图同上~

方法二-ViewGroup.MarginLayoutParams

在通过LayoutParams来改变一个变量的位置的时候,通常改变的是这个View的margin属性,所以除了使用布局的LayoutParams之外,我们还可以使用ViewGroup.MarginLaoutParams来实现同样的功能。

  // 计算偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                setLayoutParams(layoutParams);
                break;


使用ViewGroup.MarginLayoutParams更加方便,不需要考虑父布局的类型,当然了这两者的本质都是一样的。


scrollTo和scrollBy

概述


在一个View中,系统提供了scrollTo 、scrollBy两种方式来改变一个View的位置。

顾名思义,

  • scrollTo(x,y)表示移动到一个具体的坐标点 (x,y).
  • scrollBy(dx,dy)表示移动的增量为dx,dy.


需要注意的是:


scrollTo和scrollBy方法移动的是View的content,即让View中的内容移动,如果在ViewGroup中使用scrollTo和scrollBy方法,那么移动的将是所有的子View,但如果在View中使用,那么移动的将是View的内容,比如TextView,content就是它的文本,ImageView,content就是它的Drawable对象。

要实现跟随手指移动而滑动的效果,必须将偏移量设置为负值。

如果将scrollBy中的参数dx和dy设置为正数,那么content将向坐标的负方向移动,设置为负数,content将向坐标轴的正方向移动。

在使用绝对坐标系时,也可以通过scrollTo来实现相同的效果


Code

关键自定义View

package com.turing.base.android_hero.chapter5_Scroll;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
 * MyApp
 *
 * @author Mr.Yang on 2016-04-10  12:32.
 * @version 1.0
 * @desc
 */
public class DragView_scrollToscrollBy extends View {
    private int lastX;
    private int lastY;
    public DragView_scrollToscrollBy(Context context) {
        super(context);
        initViewBackgroundColor();
    }
    public DragView_scrollToscrollBy(Context context, AttributeSet attrs) {
        super(context, attrs);
        initViewBackgroundColor();
    }
    public DragView_scrollToscrollBy(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initViewBackgroundColor();
    }
    private void initViewBackgroundColor() {
        setBackgroundColor(Color.BLUE);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                ((View) getParent()).scrollBy(-offsetX,-offsetY);
                break;
        }
        return true;
    }
}

效果图同上~


Scroller

概述

上面说到了scrollTo、scrollby方法,就不得不提一下Scroller类。


总体来讲,scrollTo scrollBy方法,子View的移动都是瞬间的,在事件执行的时候平移已经完成了,而Scroller类可以实现平滑移动的效果,而不是在瞬间完成的移动。


演示:

子View随着手指的滑动而滑动,在手指离开屏幕时,让子View平滑的移动到初始位置,即屏幕的左上角。


使用Scroller一般需要三个步骤:


初始化Scroller

重写computerScrol方法,实现模拟滑动

startScroll开启模拟过程


Code

package com.turing.base.android_hero.chapter5_Scroll;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
/**
 * MyApp
 *
 * @author Mr.Yang on 2016-04-10  13:15.
 * @version 1.0
 * @desc
 */
public class DragViewScroller extends View {
    private int lastX;
    private int lastY;
    private Scroller mScroller;
    public DragViewScroller(Context context) {
        super(context);
        ininView(context);
    }
    public DragViewScroller(Context context, AttributeSet attrs) {
        super(context, attrs);
        ininView(context);
    }
    public DragViewScroller(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ininView(context);
    }
    private void ininView(Context context) {
        setBackgroundColor(Color.BLUE);
        // 初始化Scroller
        mScroller = new Scroller(context);
    }
    @Override
    public void computeScroll() {
        super.computeScroll();
        // 判断Scroller是否执行完毕
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(
                    mScroller.getCurrX(),
                    mScroller.getCurrY());
            // 通过重绘来不断调用computeScroll
            invalidate();
        }
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                ((View) getParent()).scrollBy(-offsetX, -offsetY);
                break;
            case MotionEvent.ACTION_UP:
                // 手指离开时,执行滑动过程
                View viewGroup = ((View) getParent());
                mScroller.startScroll(
                        viewGroup.getScrollX(),
                        viewGroup.getScrollY(),
                        -viewGroup.getScrollX(),
                        -viewGroup.getScrollY());
                invalidate();
                break;
        }
        return true;
    }
}


效果图

20160410133824624.gif


属性动画

待出一篇博客详述~


ViewDragHelper

概述

步骤:


  • 初始化ViewDragHelper
  • 拦截事件
  • 处理computerScroll
  • 处理回调Callback


Code


请移步 本人Github-DragViewGroup


效果图

20160410145948392.gif

相关文章
|
1月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
160 4
|
1月前
|
安全 Android开发 数据安全/隐私保护
深入探讨iOS与Android系统安全性对比分析
在移动操作系统领域,iOS和Android无疑是两大巨头。本文从技术角度出发,对这两个系统的架构、安全机制以及用户隐私保护等方面进行了详细的比较分析。通过深入探讨,我们旨在揭示两个系统在安全性方面的差异,并为用户提供一些实用的安全建议。
|
3月前
|
开发工具 Android开发 Swift
安卓与iOS开发环境对比分析
在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统无疑是主角。它们各自拥有独特的特点和优势,为开发者提供了不同的开发环境和工具。本文将深入浅出地探讨安卓和iOS开发环境的主要差异,包括开发工具、编程语言、用户界面设计、性能优化以及市场覆盖等方面,旨在帮助初学者更好地理解两大平台的开发特点,并为他们选择合适的开发路径提供参考。通过比较分析,我们将揭示不同环境下的开发实践,以及如何根据项目需求和目标受众来选择最合适的开发平台。
55 2
|
24天前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文深入探讨了这两个平台的开发环境,从编程语言、开发工具到用户界面设计等多个角度进行比较。通过实际案例分析和代码示例,我们旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和个人偏好做出明智的选择。无论你是初涉移动开发领域的新手,还是寻求跨平台解决方案的资深开发者,这篇文章都将为你提供宝贵的信息和启示。
29 8
|
2月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
81 15
Android 系统缓存扫描与清理方法分析
|
28天前
|
安全 Android开发 数据安全/隐私保护
深入探索Android与iOS系统安全性的对比分析
在当今数字化时代,移动操作系统的安全已成为用户和开发者共同关注的重点。本文旨在通过比较Android与iOS两大主流操作系统在安全性方面的差异,揭示两者在设计理念、权限管理、应用审核机制等方面的不同之处。我们将探讨这些差异如何影响用户的安全体验以及可能带来的风险。
34 1
|
2月前
|
存储 Linux Android开发
Android底层:通熟易懂分析binder:1.binder准备工作
本文详细介绍了Android Binder机制的准备工作,包括打开Binder驱动、内存映射(mmap)、启动Binder主线程等内容。通过分析系统调用和进程与驱动层的通信,解释了Binder如何实现进程间通信。文章还探讨了Binder主线程的启动流程及其在进程通信中的作用,最后总结了Binder准备工作的调用时机和重要性。
Android底层:通熟易懂分析binder:1.binder准备工作
|
3月前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:技术深度分析与实践建议
本文旨在深入探讨并比较Android和iOS两大移动操作系统在安全性方面的不同之处。通过详细的技术分析,揭示两者在架构设计、权限管理、应用生态及更新机制等方面的安全特性。同时,针对这些差异提出针对性的实践建议,旨在为开发者和用户提供增强移动设备安全性的参考。
154 3
|
2月前
|
开发工具 Android开发 Swift
安卓与iOS开发环境的差异性分析
【10月更文挑战第8天】 本文旨在探讨Android和iOS两大移动操作系统在开发环境上的不同,包括开发语言、工具、平台特性等方面。通过对这些差异性的分析,帮助开发者更好地理解两大平台,以便在项目开发中做出更合适的技术选择。
|
3月前
|
安全 Linux Android开发
探索安卓与iOS的安全性差异:技术深度分析
本文深入探讨了安卓(Android)和iOS两个主流操作系统平台在安全性方面的不同之处。通过比较它们在架构设计、系统更新机制、应用程序生态和隐私保护策略等方面的差异,揭示了每个平台独特的安全优势及潜在风险。此外,文章还讨论了用户在使用这些设备时可以采取的一些最佳实践,以增强个人数据的安全。