安卓Activity上滑关闭效果实现

最近在做一个屏保功能,需要支持如图的上滑关闭功能。

因为屏保是可以左右滑动切换的,内部是一个viewpager

做这个效果的时候,关键就是要注意外层拦截触摸事件时,需要有条件的拦截,不能影响到内部viewpager的滑动处理。

以下是封装好的自定义view,继承自FrameLayout:



import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;

public class SlideCloseFrameLayout extends FrameLayout {

    /**
     * 滑动监听器
     */
    public interface OnSlideCloseListener {
        /**
         * 滑动开始时调用
         */
        void onStartSlide();

        /**
         * 滑动结束&动画结束时调用,isClose为true表示滑动关闭,为false表示滑动恢复原位
         * @param isClose
         */
        void onStopSlide(boolean isClose);
    }

    private OnSlideCloseListener onSlideCloseListener;

    private static final String TAG = "SlideCloseFrameLayout";
    private float downY = 0; // 记录手指按下时的Y坐标
    private boolean isSlideAction = false; // 标记是否为滑动关闭动作
    private VelocityTracker velocityTracker = null; // 速度跟踪器
    private float lastTranslationY = 0; // 记录上一次的TranslationY值,用于滑动时的位置更新

    public SlideCloseFrameLayout(Context context) {
        super(context);
    }

    public SlideCloseFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        try {
            int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    downY = event.getRawY();
                    if (downY > getHeight() - getHeight() / 5f) {
                        initVelocityTracker();
                        velocityTracker.addMovement(event);
                        return false; // 拦截事件
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    velocityTracker.addMovement(event);
                    velocityTracker.computeCurrentVelocity(1000);
                    float xVelocity = velocityTracker.getXVelocity();
                    float yVelocity = velocityTracker.getYVelocity();
                    if (Math.abs(yVelocity) > ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity()
                            && Math.abs(yVelocity) > Math.abs(xVelocity)) {
                        // 如果超过最小判定距离,并且Y轴速度大于X轴速度,才视为纵向滑动
                        if (yVelocity < 0) {
                            // 向下滑动
                            if (onSlideCloseListener != null) {
                                onSlideCloseListener.onStartSlide();
                            }
                            isSlideAction = true;
                            return true;
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    isSlideAction = false;
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            if (isSlideAction) {
                velocityTracker.addMovement(event);
                int action = event.getAction();
                switch (action) {
                    case MotionEvent.ACTION_MOVE:
                        float moveDistance = event.getRawY() - downY;
                        if (moveDistance < 0) { // 仅当向上滑动时处理
                            lastTranslationY = moveDistance;
                            this.setTranslationY(moveDistance);
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        velocityTracker.computeCurrentVelocity(1000);
                        float velocityY = velocityTracker.getYVelocity();
                        if (Math.abs(velocityY) > 1000 || Math.abs(lastTranslationY) > getHeight() / 5f) {
                            slideUpAndExit();
                        } else {
                            slideBack();
                        }
                        releaseVelocityTracker();
                        isSlideAction = false;
                        break;
                }
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.onTouchEvent(event);
    }

    public boolean isSlideAction() {
        return isSlideAction;
    }

    public OnSlideCloseListener getOnSlideCloseListener() {
        return onSlideCloseListener;
    }

    public void setOnSlideCloseListener(OnSlideCloseListener onSlideCloseListener) {
        this.onSlideCloseListener = onSlideCloseListener;
    }

    private void initVelocityTracker() {
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain();
        } else {
            velocityTracker.clear();
        }
    }

    private void releaseVelocityTracker() {
        if (velocityTracker != null) {
            velocityTracker.recycle();
            velocityTracker = null;
        }
    }

    private void slideUpAndExit() {
        // 执行上移退出动画
        TranslateAnimation exitAnimation = new TranslateAnimation(0, 0, getTranslationY(), -getHeight());
        exitAnimation.setDuration(300);
        exitAnimation.setFillAfter(false);
        exitAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                // 动画结束后的操作
                setVisibility(View.GONE); // 隐藏或其他逻辑
                if (onSlideCloseListener != null) {
                    onSlideCloseListener.onStopSlide(true);
                }
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
        startAnimation(exitAnimation);
        this.setTranslationY(0); // 重置TranslationY值
    }

    private void slideBack() {
        // 使用属性动画使视图回到原位
        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "translationY", getTranslationY(), 0);
        animator.setDuration(300);
        animator.start();
        animator.addListener(new Animator.AnimatorListener(){
            @Override
            public void onAnimationStart(@NonNull Animator animation) {

            }

            @Override
            public void onAnimationEnd(@NonNull Animator animation) {
                if (onSlideCloseListener != null) {
                    onSlideCloseListener.onStopSlide(false);
                }
            }

            @Override
            public void onAnimationCancel(@NonNull Animator animation) {
                if (onSlideCloseListener != null) {
                    onSlideCloseListener.onStopSlide(false);
                }
            }

            @Override
            public void onAnimationRepeat(@NonNull Animator animation) {

            }
        });
    }
}

Activity使用时,只需要把根View设置为这个自定义view,然后透明主题,透明背景,同时关闭Activity的进入退出动画,便可以实现如图效果了。

嵌套使用时,不会影响到内部的Viewpager或其他可滑动view

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux设置Nacos自启动

前提&#xff1a;已经安装好nacos应用 可参考&#xff1a;Nacos单机版安装-CSDN博客 1. 创建nacos.service 1.1 在 /lib/systemd/system 目录底下&#xff0c;新建nacos.service文件 [Unit] Descriptionnacos Afternetwork.target[Service]Typeforking# 单机启动方式&#…

OpenLayers6实战,OpenLayers实现鼠标拖拽方式绘制梯形

专栏目录: OpenLayers实战进阶专栏目录 前言 本章讲解如何使用OpenLayers实现鼠标拖拽方式绘制梯形。点击鼠标拖拽梯形,松开鼠标后绘制完成。 二、依赖和使用 "ol": "^6.15.1"使用npm安装依赖npm install ol@6.15.1使用Yarn安装依赖yarn add olvue中…

磁盘提示格式化?别慌,这里有救星!

在使用电脑的过程中&#xff0c;突然遇到磁盘提示需要格式化的情况&#xff0c;确实让人感到焦虑不已。毕竟&#xff0c;这意味着存储在磁盘上的重要数据可能面临丢失的风险。然而&#xff0c;在恐慌之余&#xff0c;我们更应该冷静应对&#xff0c;寻找有效的数据恢复方案。本…

人工智能|深度学习——基于Xception实现戴口罩人脸表情识别

一、项目背景 近年来&#xff0c;随着人工智能技术的不断发展&#xff0c;人脸表情识别已经成为了计算机视觉领域中的重要研究方向之一。然而&#xff0c;在当前的疫情形势下&#xff0c;佩戴口罩已经成为了一项必要的防疫措施&#xff0c;但是佩戴口罩会遮挡住人脸的部分区域&…

如何备份 Outline 导出的 Markdown 文件

前面&#xff0c;我撰写了两篇文章&#xff0c;介绍了&#xff1a; 《如何在本地环境安装 Outline》《使用 Outline 搭建企业、个人知识库面临的问题》 今天&#xff0c;我们继续这个话题。使用 Outline 搭建知识库&#xff0c;如何备份自己知识库内的资料。 Outline 底层使用…

VTK| VTK可视化流程+圆锥示例

要想入门vtk&#xff0c;了解vtk的可视化流程是非常有必要的。 VTK可视化流程 VTK可视化流程主要分为数据处理和渲染两个过程&#xff0c;有一张不错的可视化流程图把这个过程理解为一个舞台剧。 VTKVS运行圆锥示例 先来运行一个简单的示例代码来理解VTK运作的过程&#xff…

薅熊链Berachain测试网空投

Berachain 是 Layer1 的一条公链。 Berachain 的经济模型引入了三种代币 BERA&#xff08;原生 Gas 代币&#xff09;&#xff1a;Bearchain 的Gas TokenBGT: Berachain 的治理 Token。 在权益证明&#xff08;Proof-of-Stake&#xff09;区块链中&#xff0c;治理通证通常用于…

一套C#自主版权+应用案例的手麻系统源码

手术麻醉信息管理系统源码&#xff0c;自主版权应用案例的手麻系统源码 手术麻醉信息管理系统包含了患者从预约申请手术到术前、术中、术后的流程控制。手术麻醉信息管理系统主要是由监护设备数据采集子系统和麻醉临床系统两个子部分组成。包括从手术申请到手术分配&#xff0c…

LeetCode-热题100:142. 环形链表 II

题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内…

CURL 实例用法参考

文章目录 1. 基础使用2. 指定请求Header3. 指定Http请求方法4. 发送POST请求,添加请求体5. 发送POST请求时&#xff0c;对请求体进行编码6. 设置请求来源7. 上传二进制文件8. 构造URL查询字段9. 新增请求头标头10. 参数打印服务器响应的标头11. 跳过SSL检测12. 模拟慢网络环境&…

GEE问题——在使用sentienl数据云掩膜的时候发现出现中间连贯性的“条带”问题,如何解决?

简介 在使用sentienl+landsat数据掩膜的时候发现出现了中间连贯性的条带问题,如何解决?这里我们使用GEE出品的Landsat和sentinel数据的过程中,当我们进行云掩膜的时候出现了条带的问题。 问题 您注意到这个问题了吗? 我该如何消除它们(例如,在镶嵌前遮蔽瓦片最外层的 …

【零基础学数据结构】顺序表

目录 1.了解数据结构 什么是数据结构&#xff1f; 为什么要进行数据管理&#xff1f; 2.顺序表 顺序表概要解析&#xff1a; ​编辑顺序表的分类&#xff1a; 差别和使用优先度&#xff1a; 1.创建顺序表 1.1顺序表分为静态顺序表和动态顺序表 1.2顺序表的初始化…

【考研数学】打基础,张宇《30讲》还是武忠祥《基础篇》?

如果基础不好&#xff0c;并且已经听过了汤家凤老师的零基础课程&#xff0c;我建议再去听一听张宇30讲 因为张宇30讲讲的要比汤家凤的零基础更加进阶&#xff0c;主要是引导学生思考&#xff0c;主要是讲题比较多。武忠祥老师的课程其实也不错&#xff0c;张宇和武忠祥的主要…

Java入门学习Day04

本篇文章主要介绍了&#xff1a;如何输入数据、字符串拼接、自增自减运算符、类型转换&#xff08;int&#xff0c;double等&#xff09; CSDN&#xff1a;码银 公众号&#xff1a;码银学编程 一、键盘输入练习 Scanner是Java中的一个类&#xff0c;用于从控制台或文件中读…

如何搭建自动化测试平台

“自动化测试”有何优势&#xff1f; 具有一致性和重复性的特点&#xff0c;而且测试更客观&#xff0c;提高了软件测试的准确度、精确度和可信任度。 可将任务自动化&#xff0c;能够解放人力去做更重要的工作。 自动化测试只需要部署好相应的场景&#xff0c;如高度复杂的使…

【CKA模拟题】StorageClass实战案例分析

Useful Resources: Storage Classes , Persistent Volumes Claim , Pods 题干 For this question, please set this context (In exam, diff cluster name) kubectl config use-context kubernetes-adminkubernetes Create a Storage Class named fast-storage with a provis…

用于无人机小型化设计的高精度温补晶振

用于无人机小型化设计的高精度温补晶振:TG2016SMN和TG2520SMN。无人机的发展可以说是非常的迅速&#xff0c;在安防&#xff0c;农业&#xff0c;交通&#xff0c;电力&#xff0c;直播等领域经常能看到无人机大显身手。无人机的应用场最是非常的广泛&#xff0c;功能更强&…

EVM Layer2 主流解决方案

深度解析主流 EVM Layer 2 解决方案&#xff1a;zk Rollups 和 Optimistic Rollups 随着以太坊网络的不断演进和 DeFi 生态系统的迅速增长&#xff0c;以太坊 Layer 2 解决方案日益受到关注。 其中&#xff0c;zk Rollups 和 Optimistic Rollups 作为两种备受瞩目的主流 EVM&…

【学习】成为优秀的软件测试工程师需要学哪些知识

成为软件测试工程师&#xff0c;需要学习的内容非常的多&#xff0c;但是无非是这几大类&#xff0c;今天就和小编一起来看看这些知识&#xff0c;你是否都已经掌握。 01、测试环境的搭建 本部分主要是学习从操作系统开始&#xff0c;有关的计算机基础知识、软件和硬件知识、…

Python基于深度学习的人脸识别项目源码+演示视频,利用OpenCV进行人脸检测与识别 preview

​ 一、原理介绍 该人脸识别实例是一个基于深度学习和计算机视觉技术的应用&#xff0c;主要利用OpenCV和Python作为开发工具。系统采用了一系列算法和技术&#xff0c;其中包括以下几个关键步骤&#xff1a; 图像预处理&#xff1a;首先&#xff0c;对输入图像进行预处理&am…