android开发技巧Android开发学习

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

2018-06-16  本文已影响441人  var_rain
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
上一篇下一篇

猜你喜欢

热点阅读