当前位置: 首页 > news >正文

Android原生实现控件点击弹起效果方案(API28及以上)

之前在实现控件阴影时有提到过,阴影效果的实现采用的是Android原生的View的属性,拔高Z轴。Z轴会让View产生阴影的效果。

Z=elevation+ translationZ
拔高Z轴可以通过控制elevation和translationZ。
我们之前是通过elevation来单纯的控制Z轴;而translateZ,除了控制Z轴,还可以用来控制动画效果,比如我们点击按钮时希望它有一个弹起的效果,就是借助这个属性来实现。

StateAnimatorView接口

创建一个通用接口:

public interface StateAnimatorView {void setStateAnimator(StateAnimator stateAnimator);StateAnimator getStateAnimator();
}

实现接口

ShapeButton 实现 StateAnimatorView接口:

    // -------------------------------// stateAnimator// -------------------------------private StateAnimator stateAnimator = new StateAnimator(this);@Overridepublic void setStateAnimator(StateAnimator stateAnimator) {this.stateAnimator = stateAnimator;}@Overridepublic StateAnimator getStateAnimator() {return stateAnimator;}

StateAnimator

StateAnimator是一个管理View状态和相关Animator的类:

public class StateAnimator {private final ArrayList<Tuple> mTuples = new ArrayList<>();private Tuple lastMatch = null;private Animator runningAnimation = null;private WeakReference<View> viewRef;public StateAnimator(View target) {setTarget(target);}private Animator.AnimatorListener mAnimationListener = new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {if (runningAnimation == animation) {runningAnimation = null;}}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}};/*** Associates the given Animation with the provided drawable state specs so that it will be run* when the View's drawable state matches the specs.** @param specs     drawable state specs to match against* @param animation The Animation to run when the specs match*/public void addState(int[] specs, Animator animation, Animator.AnimatorListener listener) {Tuple tuple = new Tuple(specs, animation, listener);animation.addListener(mAnimationListener);mTuples.add(tuple);}/*** Returns the current {@link Animation} which is started because of a state change.** @return The currently running Animation or null if no Animation is running*/Animator getRunningAnimation() {return runningAnimation;}View getTarget() {return viewRef == null ? null : viewRef.get();}void setTarget(View view) {final View current = getTarget();if (current == view) {return;}if (current != null) {clearTarget();}if (view != null) {viewRef = new WeakReference<>(view);}}private void clearTarget() {viewRef = null;lastMatch = null;runningAnimation = null;}/*** Called by View*/public void setState(int[] state) {Tuple match = null;final int count = mTuples.size();for (int i = 0; i < count; i++) {final Tuple tuple = mTuples.get(i);if (StateSet.stateSetMatches(tuple.mSpecs, state)) {match = tuple;break;}}if (match == lastMatch) {return;}if (lastMatch != null) {cancel();}lastMatch = match;View view = (View) viewRef.get();if (match != null && view != null && view.getVisibility() == View.VISIBLE) {start(match);}}private void start(Tuple match) {match.getListener().onAnimationStart(match.animation);runningAnimation = match.animation;runningAnimation.start();}private void cancel() {if (runningAnimation != null && runningAnimation.isRunning()) {runningAnimation.cancel();runningAnimation = null;}}/*** @hide*/ArrayList<Tuple> getTuples() {return mTuples;}static class Tuple {final int[] mSpecs;final Animator animation;private Animator.AnimatorListener listener;private Tuple(int[] specs, Animator Animation, Animator.AnimatorListener listener) {mSpecs = specs;animation = Animation;this.listener = listener;}int[] getSpecs() {return mSpecs;}Animator getAnimation() {return animation;}public Animator.AnimatorListener getListener() {return listener;}}}

在初始化阴影属性的地方,初始化StateAnimation:

public static void initElevation(ShadowView view, TypedArray a, int[] ids) {int carbon_elevation = ids[0];int carbon_shadowColor = ids[1];int carbon_ambientShadowColor = ids[2];int carbon_spotShadowColor = ids[3];float elevation = a.getDimension(carbon_elevation, 0);view.setElevation(elevation);// 初始化StateAnimationif (elevation > 0)AnimUtils.setupElevationAnimator(((StateAnimatorView) view).getStateAnimator(), view);ColorStateList shadowColor = a.getColorStateList(carbon_shadowColor);view.setElevationShadowColor(shadowColor != null ? shadowColor.withAlpha(255) : null);if (a.hasValue(carbon_ambientShadowColor)) {ColorStateList ambientShadowColor = a.getColorStateList(carbon_ambientShadowColor);view.setOutlineAmbientShadowColor(ambientShadowColor != null ? ambientShadowColor.withAlpha(255) : null);}if (a.hasValue(carbon_spotShadowColor)) {ColorStateList spotShadowColor = a.getColorStateList(carbon_spotShadowColor);view.setOutlineSpotShadowColor(spotShadowColor != null ? spotShadowColor.withAlpha(255) : null);}}

按下按钮和松开按钮的状态和动画是一一对应的:

    public static void setupElevationAnimator(StateAnimator stateAnimator, final ShadowView view) {// 按下时的状态和动画{final ValueAnimator animator = ValueAnimator.ofFloat(0, 0);animator.setDuration(SHORT_ANIMATION_DURATION);animator.setInterpolator(new FastOutSlowInInterpolator());Animator.AnimatorListener animatorListener = new AnimatorListenerAdapter() {@Overridepublic void onAnimationStart(Animator animation) {float elevationOrTranslationZ = getElevationOrTranslationZ(view);animator.setFloatValues(0, elevationOrTranslationZ);}};animator.addUpdateListener(animation -> view.setTranslationZ((Float) animation.getAnimatedValue()));stateAnimator.addState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}, animator, animatorListener);}// 松开时的状态和动画{final ValueAnimator animator = ValueAnimator.ofFloat(0, 0);animator.setDuration(SHORT_ANIMATION_DURATION);animator.setInterpolator(new FastOutSlowInInterpolator());Animator.AnimatorListener animatorListener = new AnimatorListenerAdapter() {@Overridepublic void onAnimationStart(Animator animation) {float elevationOrTranslationZ = getElevationOrTranslationZ(view);animator.setFloatValues(elevationOrTranslationZ, 0);}};animator.addUpdateListener(animation -> view.setTranslationZ((Float) animation.getAnimatedValue()));stateAnimator.addState(new int[]{-android.R.attr.state_pressed, android.R.attr.state_enabled}, animator, animatorListener);}// 松开时的状态和动画{final ValueAnimator animator = ValueAnimator.ofFloat(0, 0);animator.setDuration(SHORT_ANIMATION_DURATION);animator.setInterpolator(new FastOutSlowInInterpolator());Animator.AnimatorListener animatorListener = new AnimatorListenerAdapter() {@Overridepublic void onAnimationStart(Animator animation) {animator.setFloatValues(view.getElevation(), 0);}};animator.addUpdateListener(animation -> view.setTranslationZ((Float) animation.getAnimatedValue()));stateAnimator.addState(new int[]{android.R.attr.state_enabled}, animator, animatorListener);}}

开始动画

在按下或松开按钮时,会回调按钮的drawableStateChanged():

    @Overrideprotected void drawableStateChanged() {super.drawableStateChanged();if (rippleDrawable != null && rippleDrawable.getStyle() != RippleDrawable.Style.Background)rippleDrawable.setState(getDrawableState());if (stateAnimator != null)// 这个方法会执行与状态相对应的动画stateAnimator.setState(getDrawableState());}

完整代码可查看:
ShapeButton部分

相关文章:

Android原生实现控件点击弹起效果方案(API28及以上)

之前在实现控件阴影时有提到过&#xff0c;阴影效果的实现采用的是Android原生的View的属性&#xff0c;拔高Z轴。Z轴会让View产生阴影的效果。 Zelevation translationZ 拔高Z轴可以通过控制elevation和translationZ。 我们之前是通过elevation来单纯的控制Z轴&#xff1b;而…...

【数据结构-队列 二】【单调队列】滑动窗口最大值

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【单调队列】&#xff0c;使用【队列】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…...

如何设置CentOS系统以禁用不必要的网络端口和服务?

要禁用CentOS系统中的不必要的网络端口和服务&#xff0c;可以按照以下步骤进行操作&#xff1a; 1. 查看当前正在运行的服务和端口&#xff1a;使用以下命令可以查看正在运行的服务和对应的端口号。 sudo netstat -tuln 2. 停用不必要的服务&#xff1a;根据netstat命令的输…...

【IDEA项目个别类爆红,但是项目可以正常运行】

打开项目时发现idea个别类爆红,但是项目可以正常运行 问题原因&#xff1a;Idea本身的问题&#xff0c;可能是其缓存问题&#xff0c;导致爆红 解决方案&#xff1a;重置Idea 很多时候排查不出代码问题&#xff0c;就尝试一下此操作。 选择目录&#xff1a;File–>Invalida…...

hive 之select 中文乱码

此处的中文乱码和mysql的库表 编码 latin utf 无关。 直接上案例。 有时候我们需要自定义一列&#xff0c;有时是汉字有时是字母&#xff0c;结果遇到这种情况了。 说实话看到这真是糟心。这谁受得了。 单独select 没有任何问题。 这是怎么回事呢&#xff1f; 经过一番检查&…...

优化|优化处理可再生希尔伯特核空间的非参数回归中的协变量偏移

原文&#xff1a;Optimally tackling covariate shift in RKHS-based nonparametric regression. The Annals of Statistics, 51(2), pp.738-761, 2023.​ 原文作者&#xff1a;Cong Ma, Reese Pathak, Martin J. Wainwright​ 论文解读者&#xff1a;赵进 编者按&#xff1a; …...

Netty深入浅出Java网络编程学习笔记(一) Netty入门篇

目录 一、概述 1、什么是Netty 2、Netty的优势 二、入门案例 1、服务器端代码 2、客户端代码 3、运行流程 组件解释 三、组件 1、EventLoop 处理普通与定时任务 关闭 EventLoopGroup 处理IO任务 服务器代码 客户端代码 分工细化 划分Boss 和Work 增加自定义EventLoopGroup 切换…...

自动化产线集控系统(西门子CNC 840D/840DSL远程控制)

1.1项目背景 RQQ/VF120机组目前为1人操作3台机床&#xff0c;需在机台旁监控。为了改善人员在班中劳动强度非常大的现状&#xff0c;调整好每台机床的节奏&#xff0c;以保证机床的最少的等待时间。本项目旨在通过远程监视设备运行过程关键参数&#xff0c;操作人员人员可远程监…...

MVVM 与 MVC区别和应用场景?

MVVM 和 MVC 1. MVC2. MVVM 1. MVC MVC 是 Model View Controller 的缩写 Model&#xff1a;模型层&#xff0c;是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。View&#xff1a;视图层&#xff0c;用户界面渲染逻辑&#xff0c;通常视图…...

Linux开发-Ubuntu软件源工具

开发&验证环境&#xff1a; 操作系统&#xff1a;ubuntu 20.04 软件源&#xff1a;http://archive.ubuntu.com/ubuntu 开发工具 sudo apt install vim sudo apt install git# gnu工具链 sudo apt install gcc sudo apt install g sudo apt install gdb# llvm工具链 sudo …...

环境下载地址

1. DOTNET环境下载 适用于 Visual Studio 的 .NET SDK 下载 (microsoft.com)https://dotnet.microsoft.com/zh-cn/download/visual-studio-sdks...

E. Block Sequence-Codeforces Round 903 (Div. 3)

E. Block Sequence dp题,设dp[i]表示i~n之间的数&#xff0c;需要最小删除数量 那么每一位数有两种情况&#xff0c;设数a[i]&#xff1a; 1.被删除&#xff1a;dp[i]dp[i1]1,这一位等于上一位的加一。 2.被保留&#xff1a;dp[i]min(dp[i],dp[ia[i]1]); #include<iostream…...

路由router

什么是路由? 一个路由就是一组映射关系&#xff08;key - value&#xff09;key 为路径&#xff0c;value 可能是 function 或 component 2、安装\引入\基础使用 只有vue-router3&#xff0c;才能应用于vue2&#xff1b;vue-router4可以应用于vue3中 这里我们安装vue-router3…...

学习编程-先改变心态

编程失败的天才 林一和我很久以前就认识了——我从五年级就认识他了。他是班上最聪明的孩子。如果每个人在家庭作业或考试准备方面需要帮助&#xff0c;他们都会去那里。 有趣的是&#xff0c;林一不是那种连续学习几个小时的孩子。 他的聪明才智似乎与生俱来&#xff0c;几乎毫…...

【Node.js】http 模块

1. http 模块 import http from http // 创建本地服务器接收数据 const server http.createServer((req, res) > {console.log(req.url)res.writeHead(200, { Content-Type: application/json // Content-Type: text/html;charsetutf-8 // 将内容以 html 标签和 utf-8 的…...

S/4 HANA 大白话 - 财务会计-2 总账主数据

接下来看看财务模块的一些具体操作。 总账相关主数据 公司每天运转&#xff0c;每天办公室有租金&#xff0c;有水电费&#xff0c;有桌椅板凳损坏&#xff0c;鼠标损坏要换&#xff0c;有产品买卖&#xff0c;有收入。那么所有这些都得记下来。记哪里&#xff1f;记在总账里…...

Redis根据中心点坐标和半径筛选符合的数据

目录 1.启动Redis​编辑 2.导入maven依赖 3.添加redis配置 4.编写RedisService 5.使用 6.验证 1.启动Redis 2.导入maven依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifac…...

springboot 集成 zookeeper 问题记录

springboot 集成 zookeeper 问题记录 环境 springboot - 2.7.8 dubbo - 3.1.11 dubbo-dependencies-zookeeper-curator5 - 3.1.11 模拟真实环境&#xff0c;将 windows 上的 zookeeper 迁移到虚拟机 linux 的 docker 环境 failed to connect to zookeeper server 迁移到…...

java中的接口interface

一、面向对象基本概念 Java是一种面向对象的语言&#xff0c;其中「对象」就相当于是现实世界中的一个个具体的例子&#xff0c;而「类」就相当于是一个抽象的模板&#xff0c;将抽象的概念模板转化为具体的例子的过程就叫做「实例化」。 比如说人这个概念就是一个抽象化的「…...

多个git提交,只推送其中一个到远程该如何处理

用新分支去拉取当前分支的指定commit记录&#xff0c;之后推送到当前分支远程仓库实现推送指定历史提交的功能 1.查看当前分支最近五次提交日志 git log --oneline -5 2.拉取远程分支创建临时本地分支 localbranch 为本地分支名 origin/dev 为远程目标分支 git checkout …...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

路由基础-路由表

本篇将会向读者介绍路由的基本概念。 前言 在一个典型的数据通信网络中&#xff0c;往往存在多个不同的IP网段&#xff0c;数据在不同的IP网段之间交互是需要借助三层设备的&#xff0c;这些设备具备路由能力&#xff0c;能够实现数据的跨网段转发。 路由是数据通信网络中最基…...

验证redis数据结构

一、功能验证 1.验证redis的数据结构&#xff08;如字符串、列表、哈希、集合、有序集合等&#xff09;是否按照预期工作。 2、常见的数据结构验证方法&#xff1a; ①字符串&#xff08;string&#xff09; 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...