Android中级——滑动分析

news2025/2/24 10:21:20

Srcoll

  • Android坐标系
  • 视图坐标系
  • 常见方法
  • 实现滑动
    • layout()
    • offsetLeftAndRight()和offsetTopAndBottom()
    • LayoutParams
    • scrollTo()与scrollBy()
    • Scroller
    • VierDragHepler

Android坐标系

将屏幕左上角的顶点作为Android坐标系的原点,向右为X轴正方向,向下为Y轴正方向

在这里插入图片描述

可通过如下方式获取控件在Android坐标系的(x, y)坐标

int[] location = new int[2];
getLocationOnScreen(location);

或通过

@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getRawX();
    int y = (int) event.getRawY();
}

视图坐标系

描述子视图在父视图中的位置关系,将父视图左上角作为坐标系原点

在这里插入图片描述
可通过如下方式获取控件在视图坐标系的(x, y)坐标

@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
}

常见方法

View中获取坐标的方法有

  • getTop():View顶边到其父布局顶边的距离
  • getLeft():View左边到其父布局左边的距离
  • getTop():View右边到其父布局左边的距离
  • getTop():View底边到其父布局顶边的距离

在这里插入图片描述

MotionEvent中获取坐标的方法有

  • getX():点击事件距离控件左边的距离,即视图坐标
  • getX():点击事件距离控件顶边的距离,即视图坐标
  • getX():点击事件距离屏幕左边的距离,即绝对坐标
  • getX():点击事件距离控件顶边的距离,即绝对坐标

实现滑动

基本思想是:当触摸View时记下坐标,当手指移动时记下移动后的坐标,从而获取偏移量修改View的坐标,不断重复实现滑动

layout()

布局如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.demo.demo0.MyView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/colorPrimaryDark" />

</LinearLayout>

在View移动时计算偏移量,并加到layout()方法中(若使用绝对坐标,每次都需重新设置初始坐标)

public class MyView extends View {

    private static final String TAG = MyView.class.getSimpleName();
    private int lastX;
    private int lastY;

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        /*int x = (int) event.getRawX();
        int y = (int) event.getRawY();*/
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                layout(getLeft() + offsetX,
                        getTop() + offsetY,
                        getRight() + offsetX,
                        getBottom() + offsetY);
                /*lastX = x;
                lastY = y;*/
                break;
        }
        return true;
    }
}

offsetLeftAndRight()和offsetTopAndBottom()

这两个方法只需传入偏移量,效果与Layout()一样

offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);

LayoutParams

使用LayoutParams修改Margin加上偏移量,效果同上

LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);

如上,获取LayoutParams时需要根据父布局的类型,或通过ViewGroup.MarginLayoutParams,这样就无需考虑父布局

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;

scrollTo()与scrollBy()

  • scrollTo(x, y) 表示移动到坐标点(x, y)
  • scrollBy(dx, dy) 表示移动的增量为 dx、dy

但上面方法移动对象是控件的content

  • 对于ViewGroup,content是其所有子View
  • 对于View,content是其内容(如TextView中的文本)

故如果要移动View,应该要调用其父类的scrollBy()

((View) getParent()).scrollBy(offsetX, offsetY);

但是如上无法得到想要的结果,原因是scrollBy()移动的是屏幕的可视区域
在这里插入图片描述

如上,后面的矩形为画布,中间的矩形则为屏幕的可视区域,若调用scrollBy(20,10),则结果为可视区域向右下方移动(而对于Button则是向左上方移动),如下图

在这里插入图片描述

故在传入参数时应设置为负数才会使控件的content向正方向移动

((View) getParent()).scrollBy(-offsetX, -offsetY);

同理scrollTo()也是如此

Scroller

  • 若在点击事件中使用scrollBy()或scrollTo(),其会在瞬间完成
  • 而ACTION_MOVE中会不断获取偏移量从而形成连续移动效果
  • Scroller的出现就是为了实现平滑移动,原理也是通过不断的偏移
public class MyView extends View {

    private static final String TAG = MyView.class.getSimpleName();
    private int lastX;
    private int lastY;
    private Scroller mScroller;

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mScroller = new Scroller(context);
    }

    @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;
            case MotionEvent.ACTION_UP:
                View viewGroup = (View) getParent();
                mScroller.startScroll(
                        viewGroup.getScrollX(),
                        viewGroup.getScrollY(),
                        -viewGroup.getScrollX(),
                        -viewGroup.getScrollY());
                invalidate();
                break;
        }
        return true;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {    //判断是否完成滑动
            ((View) getParent()).scrollTo(
                    mScroller.getCurrX(),
                    mScroller.getCurrY());
            invalidate();   //computeScroll()会在draw()方法中调用,通过不断重绘实现平滑移动
        }
    }
}

如上实现在ACTION_UP时,将控件回归原位,利用Scroller和computeScroll(),通过不断获取当前滚动值,调用scrollTo(),实现平滑移动

VierDragHepler

如下实现在ViewGroup中,将其2个子View分为Menu和Main,当侧滑拖动Main时显示Menu

public class DragViewGroup extends FrameLayout {

    private ViewDragHelper mViewDragHelper;
    private View mMenuView, mMainView;
    private int mWidth;

    public DragViewGroup(Context context) {
        this(context, null);
    }

    public DragViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMenuView = getChildAt(0);  //将传入的View分为Menu和Main
        mMainView = getChildAt(1);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = mMenuView.getMeasuredWidth();  //获取Menu的宽度,暂未使用
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //将事件拦截传给ViewDragHelper
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //将触摸事件传给ViewDragHelper
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            //指示哪个View可以被拖动,为true则所有View都可
            return mMainView == child;
        }

        //是否需要水平滑动,为0则不需要,当不需要处理padding时可直接返回left
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left;
        }

        //是否需要垂直滑动,同上
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return 0;
        }

        //拖动结束后调用
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if (mMainView.getLeft() < 500) {    //滑动距离不超过500,菜单将回滚
                mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
            } else {
                //打开菜单
                mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
            }
            ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
        }
    };

    @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
}

布局如下,在内部放置2个不同颜色全屏的TextView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <com.demo.demo0.DragViewGroup
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/menu"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorAccent" />

        <TextView
            android:id="@+id/main"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorPrimary" />
    </com.demo.demo0.DragViewGroup>

</LinearLayout>

对其进行侧滑,效果如图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uni-app中uni-ui组件库的使用

介绍uni-ui是DCloud提供的一个跨端ui库&#xff0c;它是基于vue组件的、flex布局的、无dom的跨全端ui框架。uni-ui不包括基础组件&#xff0c;它是基础组件的补充特点高性能&#xff08;自动差量更新数据&#xff0c;优化逻辑层和视图层通讯折损&#xff0c;背景停止&#xff0…

Leetcode力扣秋招刷题路-0337

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 337. 打家劫舍 III&#xff08;Mid&#xff09; 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口&#xff0c;我们称之为 root 。 除了 root 之外&#xff0c;每栋房子有且只有一…

ESP32+Arduino+OLED+u8g2播放视频

1、思路分析 ESP32采用Arduino开发&#xff0c;结合u8g2模块可以很方便地实现在oled上显示图片。因此&#xff0c;只需要将一个视频拆开成一帧帧&#xff0c;然后循环显示即可。 然而&#xff0c;有几个问题&#xff1a; 视频太大&#xff0c;esp32的flash无法存下怎么办&…

DynaSLAM-8 DynaSLAM中双目运行流程(Ⅱ):初始化SLAM系统部分System.cc

目录 1.回忆 2.System::System 1.回忆 上篇博客中我们讲述了DynaSLAM中初始化Mask R-CNN网络部分的代码。 这篇博客我们讲述初始化DynaSLAM除Mask R-CNN网络部分以外的代码。 2.System::System 初始化Mask R-CNN网络后&#xff0c;程序执行&#xff1a; // Create SLAM syst…

MongoDB 4.0支持事务了,还有多少人想用MySQL呢?

目录一、MongoDB 不支持事务&#xff1f;二、什么是事务&#xff1f;三、ACID的定义四、如何使用事务五、重要参数简介1、时间限制2、oplog大小限制六、连接池 数据库连接的缓存1、MongoDB查询数据五步走2、MongoDB连接池的参数配置七、聚合框架八、MongoDB文档格式设计1、限制…

【八大数据排序法】插入排序法的图形理解和案例实现 | C++

第十六章 插入排序法 目录 第十六章 插入排序法 ●前言 ●认识算法 ●一、插入排序法是什么&#xff1f; 1.简要介绍 2.图形理解 3.算法分析 ●二、案例实现 1.案例一 ●总结 前言 排序算法是我们在程序设计中经常见到和使用的一种算法&#xff0c;它主要是将…

MySQL【left join、right join、inner join】详细用法

参考链接&#xff1a;mysql的left join和inner join的详细用法https://blog.csdn.net/weixin_45906830/article/details/111133181 1. inner join&#xff1a;内连接&#xff1a;显示两个表中有联系的所有数据。 通俗讲&#xff1a;inner join 查找的数据是左右两张表共有的。 …

【C语言练习】字符串旋转你会嘛?

目录&#x1f36c;题目描述&#xff1a;&#x1f36d;思路一&#xff1a;&#x1f361;代码优化&#xff1a;&#x1f36d;思路二&#xff1a;&#x1f36c;题目描述&#xff1a;&#x1f36d;思路一&#xff1a;&#x1f36d;思路二&#xff1a;&#x1f36c;题目描述&#xf…

车辆控制器的 Fail Safe功能介绍

Fail Safe概要 在漆黑的夜路上&#xff0c;一辆开着头灯的汽车经过。 如果控制前照灯的控制器在这种情况下发生故障怎么办&#xff1f; 大灯会熄灭&#xff0c;造成危险吗&#xff1f; 不。 在这种情况下&#xff0c;控制器的“Fail Safe”被激活&#xff0c;前照灯保持其先前的…

企业需要一个数字体验平台(DXP)吗?

数字体验平台是一个软件框架&#xff0c;通过与不同的业务系统喝解决方案集成&#xff0c;帮助企业和机构建立、管理和优化跨渠道的数字体验。帮助企业实现跨网站、电子邮件、移动应用、社交平台、电子商务站点、物联网设备、数字标牌、POS系统等传播内容&#xff0c;除了为其中…

termux入门安装

下载安装 请使用F-Droid 的Termux&#xff0c;GooglePlay的 Termux 可能存在一些问题。 下载地址&#xff1a;https://f-droid.org/en/packages/com.termux/ 下载完成在安卓手机上直接安装Termux的apk文件就可以了。 termux换源 新版本的termux换源一条命令就可以超简单&…

【C++之类和对象】初识类和对象

目录前言一、面向对象VS面向过程二、类三、类的定义四、类的访问限定符五、封装六、C中的用struct和用class定义的类有何不同&#xff1f;七、类的作用域八、类的实例化九、计算类对象的大小十、this指针前言 C是一门面向对象的语言&#xff0c;之前学习的C语言是一种面向过程的…

对epoll的重新学习【附源码】

目录 一、概述 二、使用 三、API 3.1 epoll_create(int size) 3.2 epoll_ctl(int epfd,int op, int fd. struct epoll_event *event) 3.3 epoll_wait(int epfd, struct peoll_event *events, int maxevents, int timeout) 3.4 *ssize_t read(int fd, void buf, size_t c…

python模块之codecs

python 模块codecs python对多国语言的处理是支持的很好的&#xff0c;它可以处理现在任意编码的字符&#xff0c;这里深入的研究一下python对多种不同语言的处理。 有一点需要清楚的是&#xff0c;当python要做编码转换的时候&#xff0c;会借助于内部的编码&#xff0c;转换…

Spark读取Hive数据的两种方式与保存数据到HDFS

Spark读取Hive数据的两种方式与保存数据到HDFS Spark读取Hive数据的方式主要有两种 1、 通过访问hive metastore的方式&#xff0c;这种方式通过访问hive的metastore元数据的方式获取表结构信息和该表数据所存放的HDFS路径&#xff0c;这种方式的特点是效率高、数据吞吐量大、…

规则引擎-drools-4-动态生成drl文档

文章目录drools 引擎工作原理动态生成drl文件示例步骤模板文件 decision_rule_template.drt生成规则文件serviceDecisionNodeFact实体对象生成的drl字符串如下KieHealper 执行动态生成drl文件的原理实际应用过程中&#xff0c;很多时候&#xff0c;规则不是一成不变的&#xff…

54.Isaac教程--RealSense相机

RealSense相机 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录RealSense相机RealsenseCamera Codelet示例应用程序故障排除固件注意事项通过 USB 3.0 电缆使用 USB 3.0 端口x86_64 Linux 主机设置设置电源模型英特尔RealSense 435 摄像头…

分享159个ASP源码,总有一款适合您

ASP源码 分享159个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 159个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1EaQuRA6mxyylrNWLq8iKVA?pwdaljz 提取码&#x…

springmvc知识巩固

文章目录回顾spring知识前言什么是SpringMVCSpringMVC的优点SpringMVC的常用注解Controller注解的作用ResponseBody注解的作用SpringMVC重定向和转发SpringMVC主要组件SpringMVC的执行流程回顾spring知识 上篇整理了“spring知识巩固”常见面试题&#xff0c;有需要的伙伴请点…

Java基础:源码讲解Collection及相关实现List、Set、Queue

1 缘起 说到Java第一问&#xff0c;很多人的第一反应是三大特性&#xff0c;那么接下来&#xff0c;可能就是集合了。 Collection是Java必知必会&#xff0c;即使没有系统学习&#xff0c;在实际的开发过程中&#xff0c;Collection也是应用最广泛的。 当然&#xff0c;一般的…