最近产品给了一个竞品App的Toast动画,希望开发可以去实现它,经过一段时间的深(不)思(停)熟(百)虑(度)之后,发现事情其实并不简单,所以这里记录一下关于Android~Toast动画实现的相关问题。

首先产品动画大概长这样:

https://live.csdn.net/v/172131

动画非常简单,大概可以分解为:

  • 弹出:位置平移和透明度增加;

  • 回弹:位置回弹和透明度减少;

其实在我们实际项目中,我们肯定希望这个Toast可以动态配置,弹出的位置,宽高以及弹出的动画等等,基于这些网络上一些开源的Toast框架也不少,大部分都可以满足,重复的轮子咱也不必重复造,这篇文章的目的主要是对Toast动画实现的核心进行讨论,各有长短,对于Android的各个版本的适配情况。

目前实现Toast动画主流实现大概有三种方式:WindowManager,反射获取TN对象以及LayoutTransition。

一、WindowManger

其实Toast的底层也是通过WindowManger来实现的,并且设置WindowManager的type为TYPE_TOAST,咱要是自己设置Toast动画,必定要自己实现WindowManger,所以核心代码为:

...
//首先获取WindowManger对象
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
...
mToast = new Toast(getContext());
mToast.setView(layout);
mParams = new WindowManager.LayoutParams();
mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mParams.format = PixelFormat.TRANSLUCENT;
mParams.windowAnimations = R.style.AgreeToastStyle;//设置进入退出动画效果
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {mParams.type = WindowManager.LayoutParams.TYPE_TOAST;
}
mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
mParams.y = mContext.getResources().getDimensionPixelOffset(R.dimen.dp_92);
​
public synchronized void show(@Nullable String msg) {if (!isShow && !TextUtils.isEmpty(msg)) {isShow = true;mBinding.tvTitle.setText(msg);mWindowManager.addView(mToast.getView(), mParams);mTimer = new Timer();mTimer.schedule(new TimerTask() {@Overridepublic void run() {isShow = false;mWindowManager.removeView(mToast.getView());}}, mDuration);}
}

嗯嗯嗯,写好了,快乐了哦,下班。。。

Boom~

android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@e44fd78 -- permission denied for window type 2038at android.view.ViewRootImpl.setView(ViewRootImpl.java:1024)at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:428)at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:118)at com.wei.campus_today.ui.widget.AgreeToast.show(AgreeToast.java:88)at com.wei.campus_today.ui.widget.AgreeToast.show(AgreeToast.java:101)at com.wei.campus_today.ui.activity.LoginActivity.checkoutAgreeSelected(LoginActivity.java:125)at com.wei.campus_today.ui.activity.LoginActivity.onClick(LoginActivity.java:161)at android.view.View.performClick(View.java:7192)at android.view.View.performClickInternal(View.java:7166)at android.view.View.access$3500(View.java:824)at android.view.View$PerformClick.run(View.java:27592)at android.os.Handler.handleCallback(Handler.java:888)at android.os.Handler.dispatchMessage(Handler.java:100)at android.os.Looper.loop(Looper.java:213)at android.app.ActivityThread.main(ActivityThread.java:8178)at java.lang.reflect.Method.invoke(Native Method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101)

首先在Android8.0以上,WindowManger的Type必须设置TYPE_APPLICATION_OVERLAY,再者还得动态获取权限:android.permission.SYSTEM_ALERT_WINDOW,但是在竞品App中,弹出这个Toast的时候,并没有要求获取Window权限啊~

二、反射获取TN对象

如果咱这不能自定义Window Manger来实现动画,那么咱可不可以获取Toast依赖的WindowManger,直接设置动画呢?那么这样我们不必执行Toast的时候,需要获取Window权限。

说干就干,干完早点干饭~

打开Toast源码,发现其中有一个TN对象,其中持有WindowManager的对象,那么咱可以使用反射,设置TN中WindowManger的windowAnimations为我们自定义的动画ID。

    public synchronized void show(@Nullable String msg) {if (!isShow && !TextUtils.isEmpty(msg)) {isShow = true;try {Object mTN;Field field = mToast.getClass().getDeclaredField("mTN");field.setAccessible(true);mTN = field.get(mToast);if (mTN != null) {Field field1 = mTN.getClass().getField("mParams");field1.setAccessible(true);Object mParams = field1.get(mTN);if (mParams != null&& mParams instanceof WindowManager.LayoutParams) {WindowManager.LayoutParams params = (WindowManager.LayoutParams) mParams;params.windowAnimations = R.style.AgreeToastStyle;}}} catch (Exception e) {e.printStackTrace();}mToast.show();}}

嗯嗯,运行好了,没问题,下班~

但是Android10.0上运行,效果还是没了,还是基础效果,打开面板一看报错了:

java.lang.NoSuchFieldException: No field mTN in class Landroid/widget/Toast; (declaration of 'android.widget.Toast' appears in /system/framework/framework.jar!classes3.dex) at java.lang.Class.getDeclaredField(Native Method)

看来今儿是没办法按时下班了,默默的打开了美团~

再次打开Toast源码,仔细的开始研究...

 private static class TN extends ITransientNotification.Stub {@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)private final WindowManager.LayoutParams mParams;}

其实这个也是网上说的Android系统的灰色权限,高于28的版本没办法通过反射拿到这个对象,那么现在只剩下唯一的一条路了,通过自定义View实现LayoutTransition

三、LayoutTransition

咱可以完全的抛弃掉Toast,通过自定义View实现一个基础的TextView,在show的时候通过ViewGroup.addView将基础的TextView加入到容器中,这时候可以设置ViewGroup的LayoutTransition实现动画。但是这样的逻辑会有两个问题:

  • 过度依赖ViewGroup,若不是在show的时候,需要传入Activity/Fragment,然后通过findViewById去获取根布局,然后添加自定义View?

  • 如果依赖的Activity/Fragment没有设置setContentView,那么如何通过通过findViewById去获取ViewGroup呢?

1.解决过度依赖Activity/Fragment问题:

既然选择了这个方案,那么在展示自定义View的时候必定需要ViewGroup,为了避免耦合,那么咱可以集成Application.ActivityLifecycleCallbacks,实现Activity栈,在Application中注册,即可获取栈顶的Activity来展示这个View~

2.解决依赖的Activity/Fragment没有设置setContentView,如何获取ViewGroup?

回答这个问题的时候,我们必须知道activity的窗口层级

我们可以通过android.R.id.content来获取Activity的根布局的FrameLayout,无论你设不设置SetContentView都可以拿到ViewGroup

关于LayoutTransition一些介绍,在ViewGroup.addView/removeView的时候,可以将动画带给需要的View。

相关资料

  • activity界面架构即activity视图层结构

  • Toast添加动画

  • LayoutTransition那些事儿

关于Android项目中的Toast那些动画实现方式相关推荐

  1. 安卓:Android项目中三种依赖的添加方式

    添加本地依赖 首先将所需的 jar 或者 aar 包放在libs文件夹下. 方式1(适合jar) 右击jar包,选择Add As Library,最后sync. 方式2 (适合jar 和 aar) 在 ...

  2. Android项目中最火最常用的优秀开源项目(很有用)

    Android项目中最火最常用的优秀开源项目 分类 详细 框架名称 简介 Star 数 最近 更新 UI 刷新 SmartRefreshLayout Android 智能下拉刷新框架 7.7k 1天 ...

  3. android 自定义刷新控件,Android开发中MJRefresh自定义刷新动画效果

    有时候我们对自己开发的项目经常不满意,但是我们要达到自定义刷新动画的效果有一定的难度,别着急,下面爱站技术频道和大家分享Android开发中MJRefresh自定义刷新动画效果,一起来学习吧! [一] ...

  4. Android 项目中文件夹的作用

    Android 项目中文件夹的作用 1. src:存放所有的*.java源程序. 2. gen:为ADT插件自动生成的代码文件保存路径,里面的R.java将保存所有的资源ID. 3. assets:可 ...

  5. android项目中自定义顶部标题栏,Android项目中自定义顶部标题栏

    Android项目中自定义顶部标题栏 下面给大家详细介绍android中自定义顶部标题栏的思路及实现方式 先来图: 思路及实现步骤 1.定义标题栏布局 2.自定义TitleActivity控制标题栏按 ...

  6. android使用webview上传文件,Android项目中如何在webview页面中上传文件

    Android项目中如何在webview页面中上传文件 发布时间:2020-11-26 15:56:27 来源:亿速云 阅读:68 作者:Leah 本篇文章为大家展示了Android项目中如何在web ...

  7. Android项目中创建编译期的注解

    ==注解 生命周期为RetentionPolicy.RUNTIME,可在运行时通过反射获取. 生命周期为RetentionPolicy.CLASS, 编译期处理的注解,可以使用APT(Annotati ...

  8. Android项目中出现的Plugin with id ‘kotlin-android‘ not found解决方法

    Android项目中出现的Plugin with id 'kotlin-android' not found解决方法 参考文章: (1)Android项目中出现的Plugin with id 'kot ...

  9. flutter打开android界面,在已有Android项目中使用Flutter

    实现效果,在已存在的android项目中接入flutter,即android调用开启flutter页面(使用android打开flutter的指定页面),flutter调用原生android方法 步骤 ...

最新文章

  1. Timer 和TimerTask 的定时任务入门
  2. 9个用于构建容错系统的开源工具
  3. 【报错】:Char 5: error: non-void f
  4. 前端有未来吗?听我娓娓道来!
  5. 撩妹java代码_Java程序媛深入浅出设计模式中的撩妹神技--中篇
  6. 小米2怎样启动ANdroid?,小米2S如何打开后盖? 小米手机开盖技巧介绍(小米手机通用)...
  7. (VBA) Get String
  8. 基于RV1126 Video分析-----驱动各模块总览
  9. 丢番图(Diophantine)方程MATLAB求解
  10. 优动漫PAINT中误删工具怎么办?
  11. ibm邮箱连接不到服务器,IBM i 安全邮件配置和常见故障排除方法
  12. 直线回归和相关------(二)直线回归的假设测验和区间估计以及matlab实现
  13. 如何使用Python轻松解决TSP问题(遗传算法)
  14. Wiener Filtering
  15. 国家的mysql表_中国省份数据库+世界国家名数据库
  16. [everydayNote] 零零散散不成篇
  17. KA算法:一种低复杂度的预编码/接收机设计思路
  18. 思科路由器限速设置全解
  19. Linux上基于 Golang 实现 KeyLogger 按键记录
  20. my97DatePicker选择年、季度、月、周、日

热门文章

  1. 移动电源充电宝新国标GB/T 35590-2017检测报告测试项目
  2. scrapy抓取淘宝女郎 1
  3. VSCode C++环境配置及测试运行
  4. Geogebra 教程之 02 Geogebra初学者的 8 个基本要素
  5. 励志!从中专生到教授、国家杰青,近日,他又当选院士!
  6. YYKit Demo
  7. 公布Windows版Flutter
  8. 基于ZigBee的智能家居设计与实现—CC2530开发
  9. Multi-Horizon Time Series Forecasting with Temporal Attention Learning
  10. 架设linux服务器下的samba,Linux 服务器搭建之Samba服务