当前位置: 首页 > 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部分

http://www.lryc.cn/news/191420.html

相关文章:

  • 【数据结构-队列 二】【单调队列】滑动窗口最大值
  • 如何设置CentOS系统以禁用不必要的网络端口和服务?
  • 【IDEA项目个别类爆红,但是项目可以正常运行】
  • hive 之select 中文乱码
  • 优化|优化处理可再生希尔伯特核空间的非参数回归中的协变量偏移
  • Netty深入浅出Java网络编程学习笔记(一) Netty入门篇
  • 自动化产线集控系统(西门子CNC 840D/840DSL远程控制)
  • MVVM 与 MVC区别和应用场景?
  • Linux开发-Ubuntu软件源工具
  • 环境下载地址
  • E. Block Sequence-Codeforces Round 903 (Div. 3)
  • 路由router
  • 学习编程-先改变心态
  • 【Node.js】http 模块
  • S/4 HANA 大白话 - 财务会计-2 总账主数据
  • Redis根据中心点坐标和半径筛选符合的数据
  • springboot 集成 zookeeper 问题记录
  • java中的接口interface
  • 多个git提交,只推送其中一个到远程该如何处理
  • uniapp中input的disabled属性
  • Jmeter连接mysql数据库详细步骤
  • Xcode 14.3.1build 报错整理
  • TensorFlow入门(十三、动态图Eager)
  • 批量执行insert into 的脚本报2006 - MySQL server has gone away
  • 翻译docker官方文档(残缺版)
  • PySpark 概述
  • 『heqingchun-ubuntu系统下Qt报错connot find -lGL解决方法』
  • 代码整洁之道:程序员的职业素养(十六)
  • OSPF的原理与配置
  • uni-app : 生成三位随机数、自定义全局变量、自定义全局函数、传参、多参数返回值