美文网首页android开发技巧Android开发学习
[Surprise Android] 应用内全局消息提示

[Surprise Android] 应用内全局消息提示

作者: var_rain | 来源:发表于2018-06-16 17:38 被阅读441次
    1500286923_459579.jpg

    前言

    最近因为公司一个需求,要弄个那啥全局消息提醒的功能,简单来说就是APP在任意一个Activity内都能接收到消息提醒,并且能点击,如此想来简单的Toast肯定不行的,因为Toast不能点击,而且嘛,有时候在应用外也会显示,这就很不友好,所以呢,抄起锄头,开始挖坑...

    效果预览

    app_tips.gif

    技术浅析

    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加点动画,美滋滋~~

    5a10bb7eca806538e5d832f49edda144ad34822f.jpg

    实现方法

    首先我们要创建一个类继承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);
        }
    }
    

    干货分享

    项目地址 https://github.com/1934016928/FloatView

    ce0b23381f30e92424850ad645086e061d95f73c.jpg

    相关文章

      网友评论

      • April丶:🙄写得不错,但现在它是我的了。
        var_rain:@April丶 你开心就好

      本文标题:[Surprise Android] 应用内全局消息提示

      本文链接:https://www.haomeiwen.com/subject/wlsbeftx.html