当前位置: 首页 > 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 …...

uniapp中input的disabled属性

uniapp中input的disabled属性&#xff1a; 小程序中兼容性好&#xff1b; 在H5中兼容性差&#xff1b; 在H5中使用uniapp的input的disabled属性&#xff0c;属性值只能是true或false&#xff0c;如果为0&#xff0c; "都会为true&#xff1b; <input class"in…...

Jmeter连接mysql数据库详细步骤

一、一般平常工作中使用jmeter 连接数据库的作用 主要包括&#xff1a; 1、本身对数据库进行测试&#xff08;功能、性能测试&#xff09;时会需要使用jmeter连接数据库 2、功能测试时&#xff0c;测试出来的结果需要和数据库中的数据进行对比是否正确一致。这时候可以通过j…...

Xcode 14.3.1build 报错整理

1、Command PhaseScriptExecution failed with a nonzero exit code 2、In /Users/XX/XX/XX/fayuan-mediator-app-rn/ios/Pods/CocoaLibEvent/lib/libevent.a(buffer.o), building for iOS Simulator, but linking in object file built for iOS, file /Users/XX/XX/XX/fayuan…...

TensorFlow入门(十三、动态图Eager)

一个图(Graph)代表一个计算任务,且在模型运行时,需要把图放入会话(session)里被启动。一旦模型开始运行,图就无法修改了。TensorFlow把这种图一般称为静态图。 动态图是指在Python中代码被调用后,其操作立即被执行的计算。 它与静态图最大的区别是不需要使用session来建立会话…...

批量执行insert into 的脚本报2006 - MySQL server has gone away

数据库执行批量数据导入是报“2006 - MySQL server has gone away”错误&#xff0c;脚本并没有问题&#xff0c;只是insert into 的批量操作语句过长导致。 解决办法&#xff1a; Navicat ->工具 ->服务器监控->mysql ——》变量 修改max_allowed_packet大小为512…...

翻译docker官方文档(残缺版)

Build with docker(使用 Docker 技术构建应用程序或系统镜像) Overview (概述) 介绍&#xff08;instruction&#xff09; 层次结构&#xff08;Layers&#xff09; The order of Dockerfile instructions matters. A Docker build consists of a series of ordered build ins…...

PySpark 概述

文章最前&#xff1a; 我是Octopus&#xff0c;这个名字来源于我的中文名--章鱼&#xff1b;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github &#xff1b;这博客是记录我学习的点点滴滴&#xff0c;如果您对 Python、Java、AI、算法有兴趣&#xff0c;可以关注我的…...

『heqingchun-ubuntu系统下Qt报错connot find -lGL解决方法』

ubuntu系统下Qt报错connot find -lGL解决方法 问题&#xff1a; Qt报错 connot find -lGL collect2:error:ld returned 1 exit status 解决方式&#xff1a; cd /usr/lib/x86_64-linux-gnu查看一下 ls | grep libGLlibGLdispatch.so.0 libGLdispatch.so.0.0.0 libGLESv2.so.…...

代码整洁之道:程序员的职业素养(十六)

辅导、学徒期与技艺 导师的重要性在职业发展中是不可低估的。尽管最好的计算机科学学位教学计划可以提供坚实的理论基础&#xff0c;但面对实际工作中的挑战&#xff0c;年轻毕业生往往需要更多指导。幸运的是&#xff0c;有许多优秀的年轻人可以通过观察和模仿他们的导师来快…...

OSPF的原理与配置

第1章 OSPF[1] 本章阐述了OSPF协议的特征、术语&#xff0c;OSPF的路由器类型、网络类型、区域类型、LSA类型&#xff0c;OSPF报文的具体内容及作用&#xff0c;描述了OSPF的邻居关系&#xff0c;通过实例让读者掌握OSPF在各种场景中的配置。 本章包含以下内容&#xff1a; …...