安卓Activity上滑关闭效果实现

news2024/11/23 22:32:49

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

因为屏保是可以左右滑动切换的,内部是一个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

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

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

相关文章

【I.MX6ULL移植】Ubuntu-base根文件系统移植

1.下载Ubuntu16.04根文件系统 http://cdimage.ubuntu.com/ 1 2 3 4 5 2.解压ubuntu base 根文件系统 为了存放 ubuntu base 根文件系统&#xff0c;先在 PC 的 Ubuntu 系统中的 nfs 目录下创建一个名为 ubuntu_rootfs 的目录&#xff0c;命令如下&#xff1a; 【注意&…

C# 学习第五弹——语句

一、if语句 —简单if语句 —if else 语句 —if else if else 语句 1、简单if语句 if&#xff08;表达式&#xff09;{语句} &#xff08;1&#xff09;表达式必须使用圆括号括起来&#xff1b; &#xff08;2&#xff09;表达式&#xff1a;关系表达式或逻辑表达…

数字孪生|初识山海鲸可视化

哈喽,你好啊,我是雷工! 最近开始学习了解数字孪生的软件,看山海鲸可视化介绍的不错,便准备下载了试一下。 01 、概述 该软件是一套技术自主可控的、国产自研的、零代码数字孪生可视化工具集, 02、产品定位 该软件致力于数字孪生应用的推广,通过提供一站式的多快好省…

接口自动化框架搭建(五):生成allure报告

1&#xff0c;安装allure 参考连接&#xff1a; https://blog.csdn.net/lixiaomei0623/article/details/120185069 2&#xff0c;安装python的allure依赖 pip install allure-pytest或者从pycharme上安装 3&#xff0c;生成报告 执行前目录 执行测试用例 import pytest …

pulsar: kafka on pulsar之把pulsar当kafka用

一、下载协议包&#xff08;要和pulsar版本比较一致&#xff09; https://github.com/streamnative/kop/releases?q2.8.0&expandedtrue二、在pulsar的根目录创建一个protocols目录&#xff0c;将上述包放到这个目录里 三、编辑broker.conf(如果是集群)或者standalone.con…

Node爬虫:原理简介

在数字化时代&#xff0c;网络爬虫作为一种自动化收集和分析网络数据的技术&#xff0c;得到了广泛的应用。Node.js&#xff0c;以其异步I/O模型和事件驱动的特性&#xff0c;成为实现高效爬虫的理想选择。然而&#xff0c;爬虫在收集数据时&#xff0c;往往面临着诸如反爬虫机…

前端性能优化:掌握解决方案

课程介绍 我们常说性能永远是第一需求&#xff0c;作为一个前端工程师&#xff0c;不管使用什么框架&#xff0c;不管从事什么类型的网站或应用开发&#xff0c;只要是项目被用户使用&#xff0c;性能优化就永远是你需要关注的问题。通常情况下&#xff0c;工程师们在深入了解…

企业微信机器人java代码对接

如何使用群机器人 假设webhook是&#xff1a;https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa 特别特别要注意&#xff1a;一定要保护好机器人的webhook地址&#xff0c;避免泄漏&#xff01;不要分享到github、博客等可被公开查…

QMT量化策略实盘(二)交易触发定时器run_time

上一篇分享中&#xff0c;介绍了QMT量化实盘中最常用的下单函数passorder&#xff0c;和它主要的参数。 如果再结合一个交易触发函数&#xff0c;就可以实现简单的量化交易策略了&#xff01;比如下面的代码可以实现&#xff1a; 在集合竞价期间以指定价买入中信证券100股 #c…

小狐狸JSON-RPC:钱包连接,断开连接,监听地址改变

detect-metamask 创建连接&#xff0c;并监听钱包切换 一、连接钱包&#xff0c;切换地址&#xff08;监听地址切换&#xff09;&#xff0c;断开连接 使用npm安装 metamask/detect-provider在您的项目目录中&#xff1a; npm i metamask/detect-providerimport detectEthereu…

蓝桥杯23年第十四届省赛真题-填充|DFS,贪心

题目链接&#xff1a; 1.填充 - 蓝桥云课 (lanqiao.cn) 蓝桥杯2023年第十四届省赛真题-填充 - C语言网 (dotcpp.com) 说明&#xff1a; dfs就不再多说了&#xff0c;对于每个?都有0和1两个分支&#xff0c;数据范围是&#xff1a; 那么有m个 ?&#xff0c;时间复杂度就是…

️ 如何掌握服务器硬件基础知识:为高效运维打下坚实基础

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

GTC 2024 火线评论:DPU 重构文件存储访问

编者按&#xff1a;英伟达2024 GTC 大会上周在美国加州召开&#xff0c;星辰天合 CTO 王豪迈在大会现场参与了 GPU 与存储相关的最新技术讨论&#xff0c;继上一篇《GTC 2024 火线评论&#xff1a;GPU 的高效存储利用》之后&#xff0c;这是他发回的第二篇评论文章。 上一篇文章…

网络七层模型之表示层:理解网络通信的架构(六)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

服务器被挖矿了怎么办,实战清退

当我们发现服务器资源大量被占用的时候&#xff0c;疑似中招了怎么办 第一时间重启服务是不行的&#xff0c;这些挖矿木马一定是会伴随着你的重启而自动重启&#xff0c;一定时间内重新霸占你的服务器资源 第一步检查高占用进程 top -c ps -ef 要注意这里%CPU&#xff0c;如果…

企微这个工具太好用,提升企业销售业绩效果好!

在商海浮沉中&#xff0c;销售业绩的提升始终是企业的核心追求。想要把产品卖出去&#xff0c;首要任务便是吸引客户。如今&#xff0c;线上线下的销售模式已然成为主流&#xff0c;短视频社交媒体如抖音、快手等平台更是成为了流量争夺的热门战场。但面对这些平台上的海量且流…

自动发卡平台源码优化版,支持个人免签支付

源码下载地址&#xff1a;自动发卡平台源码优化版.zip 环境要求&#xff1a; php 8.0 v1.2.6◂ 1.修复店铺共享连接时异常问题 2024-03-13 23:54:20 v1.2.5 1.[新增]用户界面硬币增款扣款操作 2.[新增]前台对接库存信息显示 3.[新增]文件缓存工具类[FileCache] 4.[新增]库存同…

营销大师:小米汽车定价的道道!喝酒买车你沾了吗?——早读(逆天打工人爬取热门微信文章解读)

雷神之锤降临&#xff0c;睡不着的是车企&#xff0c;不应该是你 引言Python 代码第一篇 雷军&#xff1a;小米SU7 现已开启定购&#xff5c;人车合一&#xff0c;我心澎湃第二篇 人民日报 来啦新闻早班车要闻社会政策 结尾 “物有所值乃生存之基石&#xff0c;性价比则为选择之…

Ribbon简介

目录 一 、概念介绍 1、Ribbon是什么 2、认识负载均衡 2.1 服务器端的负载均衡 2.2 客户端的负载均衡 3、Ribbon工作原理 4、Ribbon的主要组件 IClientConfig ServerList ServerListFilter IRule Iping ILoadBalancer ServerListUpdater 5、Ribbon支持…

Vue生命周期,从听说到深入理解(全面分析)

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如设置好数据侦听&#xff0c;编译模板&#xff0c;挂载实例到 DOM&#xff0c;以及在数据改变时更新 DOM。在此过程中&#xff0c;它也会运行被称为生命周期钩子的函数&#xff0c;让开发者有机会在特定阶…