[Android骚操作] 应用内全局消息提示

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,302评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,232评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,337评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,977评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,920评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,194评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,638评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,319评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,455评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,379评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,426评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,106评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,696评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,786评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,996评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,467评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,043评论 2 341

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,280评论 25 707
  • 这是一个多么美好的一天啊!这是一个多么美好的一天啊!!充满了爱、热情、效益、感恩、贡献。 全世界最有能量 最有状态...
    游亚瑟阅读 399评论 0 2
  • 关于illustrator(Ai),相信大家一定都不陌生,平时做PPT或多或少都会用到。但是illustrator...
    大梦Power阅读 4,231评论 3 64
  • “仅仅是和你一同仰望天空,平日司空见惯的风景也会变得与众不同。”——前言【与正文无关】 “我喜欢有梦想的参赛者,你...
    锈锈老了阅读 1,096评论 0 2