前言
最近因为公司一个需求,要弄个那啥全局消息提醒的功能,简单来说就是APP在任意一个Activity内都能接收到消息提醒,并且能点击,如此想来简单的Toast肯定不行的,因为Toast不能点击,而且嘛,有时候在应用外也会显示,这就很不友好,所以呢,抄起锄头,开始挖坑...
效果预览
技术浅析
Android的Application中有一个注册Activity声明周期监控接口的方法 registerActivityLifecycleCallbacks(); 这个方法的参数是一个名叫 ActivityLifecycleCallbacks 的接口内部类,里边包含了一整套Activity的声明周期回调方法,只要有一个Activity触发了声明周期,这个接口的回调就会触发,并且传回触发声明周期方法的Activity对象,我们先来看一下接口的定义
public interface ActivityLifecycleCallbacks {
void onActivityCreated(Activity activity, Bundle savedInstanceState);
void onActivityStarted(Activity activity);
void onActivityResumed(Activity activity);
void onActivityPaused(Activity activity);
void onActivityStopped(Activity activity);
void onActivitySaveInstanceState(Activity activity, Bundle outState);
void onActivityDestroyed(Activity activity);
}
可见,该接口为我们提供了一套完整的声明周期回调,那么我们通过实现这个接口,就可以在任意时候获取到当前显示的Activity,在拿到Activity之后,我们就可以通过 activity.getWindow().getDecorView() 来获取Activity的视图组,该方法返回的是一个View对象,我们可以将其强转为ViewGroup,然后就可以随心所欲的往里边添加View或者删除View了,在给自己的View加点动画,美滋滋~~
实现方法
首先我们要创建一个类继承Application,并且实现 Application.ActivityLifecycleCallbacks 接口,然后在AndroidManifast.xml中修改为你自己的Application,之后在onCreate方法中注册回调,最后在Application中声明一个Activity类型变量,用于保存当前显示的Activity,别忘了还有获取Application实例的静态方法
public class App extends Application implements Application.ActivityLifecycleCallbacks {
/*当前对象的静态实例*/
private static App instance;
/*当前显示的Activity*/
private Activity activity;
@Override
public void onCreate() {
super.onCreate();
App.instance = this;
this.registerActivityLifecycleCallbacks(this);
}
/**
* 获取Application对象
*
* @return 返回一个App对象实例
* @see App
*/
public static App instance() {
return App.instance;
}
/**
* 显示View
*
* @param view 需要显示到Activity的视图
*/
public void showView(View view) {
/*Activity不为空并且没有被释放掉*/
if (this.activity != null && !this.activity.isFinishing()) {
/*获取Activity顶层视图,并添加自定义View*/
((ViewGroup) this.activity.getWindow().getDecorView()).addView(view);
}
}
/**
* 隐藏View
*
* @param view 需要从Activity中移除的视图
*/
public void hideView(View view) {
/*Activity不为空并且没有被释放掉*/
if (this.activity != null && !this.activity.isFinishing()) {
/*获取Activity顶层视图*/
ViewGroup root = ((ViewGroup) this.activity.getWindow().getDecorView());
/*如果Activity中存在View对象则删除*/
if (root.indexOfChild(view) != -1) {
/*从顶层视图中删除*/
root.removeView(view);
}
}
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
/*获取当前显示的Activity*/
this.activity = activity;
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}
没错,你还看到了另外两个方法,这两个方法是用来添加和删除View的,为了避免插入或删除View的时候Activity已经被释放或被销毁,所以在插入或删除View的时候需要对Activity做判断,避免出现异常.
然后我们创建一个名为 view_top_msg.xml 消息提示框的layout文件,其实就是一个简单的layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/view_top_msg_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#ff9f9f"
android:gravity="center"
android:text="@string/view_top_msg_tips"
android:textColor="#ffffff"
android:textSize="20sp" />
</LinearLayout>
现在我们需要在Activity中做调用测试,其实真正使用的时候,并不是由我们主动触发的,比如直播中的全服滚动礼物提示,这些都是注册的广播由服务器推送消息触发的,这里简单的做个测试
首先使用 LayoutInflater 通过我们的layout文件创建View对象
/*创建提示消息View*/
final View view = LayoutInflater.from(this).inflate(R.layout.view_top_msg, null);
然后创建一个动画,并绑定动画到创建的View上,还需要在动画的完成回调中删除View,避免内存堆积
/*创建属性动画,从下到上*/
ObjectAnimator bottomToTop = ObjectAnimator.ofFloat(view, "translationY", 0, -dp2px(80)).setDuration(500);
/*创建属性动画,从上到下*/
ObjectAnimator topToBottom = ObjectAnimator.ofFloat(view, "translationY", -dp2px(80), 0).setDuration(500);
/*初始化动画组合器*/
AnimatorSet animator = new AnimatorSet();
/*组合动画*/
animator.play(bottomToTop).after(topToBottom).after(2000);
/*添加动画结束回调*/
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
/*删除View*/
App.instance().hideView(view);
}
});
这里我使用了一个 dp2px() 的方法来做比例转换,暂时就不贴出这个方法,本文最后会放出完整的代码以及项目地址
接下来需要将我们自定义的消息View添加到Activity的视图组中并开始动画
/**
* 创建View并启动动画
*/
@SuppressLint("InflateParams")
private void createAndStart() {
/*创建提示消息View*/
final View view = LayoutInflater.from(this).inflate(R.layout.view_top_msg, null);
/*创建属性动画,从下到上*/
ObjectAnimator bottomToTop = ObjectAnimator.ofFloat(view, "translationY", 0, -dp2px(80)).setDuration(500);
/*创建属性动画,从上到下*/
ObjectAnimator topToBottom = ObjectAnimator.ofFloat(view, "translationY", -dp2px(80), 0).setDuration(500);
/*初始化动画组合器*/
AnimatorSet animator = new AnimatorSet();
/*组合动画*/
animator.play(bottomToTop).after(topToBottom).after(2000);
/*添加动画结束回调*/
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
/*删除View*/
App.instance().hideView(view);
}
});
/*添加View到当前显示的Activity*/
App.instance().showView(view);
/*启动动画*/
animator.start();
}
最后,在按钮的点击事件中调用此方法即可,这里就贴出个MainActivity的代码
public class MainActivity extends AppCompatActivity {
/*显示提示框按钮*/
private Button showTips;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_main);
this.initViews();
this.initActions();
}
/**
* 初始化视图控件
*/
private void initViews() {
this.showTips = findViewById(R.id.act_main_but_show_tips);
}
/**
* 初始化事件监听
*/
private void initActions() {
this.showTips.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MainActivity.this.createAndStart();
}
});
}
/**
* 创建View并启动动画
*/
@SuppressLint("InflateParams")
private void createAndStart() {
/*创建提示消息View*/
final View view = LayoutInflater.from(this).inflate(R.layout.view_top_msg, null);
/*创建属性动画,从下到上*/
ObjectAnimator bottomToTop = ObjectAnimator.ofFloat(view, "translationY", 0, -dp2px(80)).setDuration(500);
/*创建属性动画,从上到下*/
ObjectAnimator topToBottom = ObjectAnimator.ofFloat(view, "translationY", -dp2px(80), 0).setDuration(500);
/*初始化动画组合器*/
AnimatorSet animator = new AnimatorSet();
/*组合动画*/
animator.play(bottomToTop).after(topToBottom).after(2000);
/*添加动画结束回调*/
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
/*删除View*/
App.instance().hideView(view);
}
});
/*添加View到当前显示的Activity*/
App.instance().showView(view);
/*启动动画*/
animator.start();
}
/**
* 从dp单位转换为px
*
* @param dp dp值
* @return 返回转换后的px值
*/
private int dp2px(int dp) {
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}
}