• 有关 Android 应用开发中的弹窗式通知


    2020-02-03

    关键字:ToastManager、应用消息通知


    Android 应用往往少不了要与用户交互的场景。

    所谓与用户交互,就是指用户需要主动或者被动接受来自应用的消息、状态提示的场景。

    这种消息、状态的展示形式往往多种多样。但常见的也是比较合适的是弹窗式交互。

    弹窗式交互是在应用内展示的。即在应用运行过程中以1、Toast 式弹窗通知;2、对话框式弹窗通知;两种形式来与用户交互。

    其中,第 1 种交互笔者称之为“弱交互式通知”,它弹出来以后过一段时间即会自行消隐。用户只需要看,完全不用去处理,甚至可以连看都不看。

    而第 2 种笔者则称之为“强交互式通知”,它会弹出一个对话框,用户只能手动点击对话框上的相应按钮才能关掉对话框。

    这两种交互弹窗的实现可就太容易了。第一个就是 Toast,而第二个则是 Dialog。堪称是小学生都能做出来。

    但今天这篇博文,不聊实现方式。来聊聊在一款应用中应如何对待各种各样的弹窗式消息通知。

    根据笔者的经验,在整个应用中统一管理弹窗式通知是最合理的。如何统一管理呢?

    即严禁私自创建 Toast 或 Dialog 来展示,这样可能会导致同时弹出多个弹窗的情况从而引发通知混乱。

    取而代之的是所有需要弹出的通知都交由同一个通知管理类来弹出。

    有了这个统一的入口,我们就可以很方便地管控通知了。是即时弹出、是过滤、是排队弹出或是其它各种需求,都可以在这个统一的通知管理类中很方便的实现。

    笔者今天就在这里记录一下自己撰写的这么一个通知管理类 ToastManager。当然,笔者的这个类仅仅是根据自己的实际需求来实现的,并没有做到绝对的完善与完美,在此记录的主要目的是为了给自己备一下忘。

    笔者的这个 ToastManager 目前有三种弹窗:

    1、弱交互式弹窗;

    2、强交互式弹窗;

    3、强交互式选择弹窗;

    强交互式弹窗的变种版,对话框上具有“确定”与“否定”两个按钮,可以通过回调方法来通知创建者用户的选择结果。

    笔者这个 ToastManager 在本质上就是简单地对 Toast 与 AlertDialog 作一下封装而已。甚至连排队机制都还没有实现,如果你有兴趣,可以尝试着自己去实现。

    对了,还有一个很重要的。因为这个通知管理类理论上允许在任意位置调用。而 Toast 和 Dialog 是不允许在子线程中弹出的,但这种情况笔者仅仅是做了打印提示处理。正常来讲应该是将所有的通知弹出请求都转换成在主线程来弹的,但很遗憾,笔者没有去实现,实在是因为懒~

    话不多说,以下是 ToastManager 的源码:

    package com.jarwen.scanner.util;
    
    import android.app.AlertDialog;
    import android.content.Context;
    import android.graphics.drawable.ColorDrawable;
    import android.graphics.drawable.Drawable;
    import android.os.Build;
    import android.view.Gravity;
    import android.view.View;
    import android.widget.Button;
    import android.widget.GridLayout;
    import android.widget.LinearLayout;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.jarwen.scanner.R;
    import com.jarwen.scanner.ScannerApplication;
    
    public class ToastManager {
    
        private static final boolean IS_WEAK_TOAST = true;
    
        private Context context;
    
        private Toast toast;
        private int txtColor;
        private int txtSize;
        private Drawable bgDrawable;
    
        private AlertDialog dialog;
        private OnMakeChoiceResult onMakeChoiceResult;
        private OnStrongToastListener onStrongToastListener;
    
        public ToastManager(Context context){
            this.context = context;
    
            toast = new Toast(context);
            toast.setDuration(Toast.LENGTH_SHORT);
            toast.setGravity(Gravity.CENTER, 0, UnitManager.px2dp(80));
    
            txtSize = 13;
            txtColor = context.getResources().getColor(R.color.gray_dark_1);
            bgDrawable = context.getResources().getDrawable(R.drawable.round_corner_gray_r5);
        }
    
        public void toast(String msg) {
            toast(IS_WEAK_TOAST, msg);
        }
    
        public void toast(boolean isWeakToast, String msg){
            if(Thread.currentThread().getId() != 1){
                Logger.e("Cannot toast on sub-thread.");
                return;
            }
    
            dismissDialog();
    
            if(isWeakToast) {
                weakToast(msg);
            }else{
                strongToast(msg);
            }
        }
    
        public void makeChoice(String content, OnMakeChoiceResult callback){
            Logger.v("makeChoice()");
            if(Thread.currentThread().getId() != 1){
                Logger.e("Cannot toast on sub-thread.");
                return;
            }
    
            dismissDialog();
    
            if(onMakeChoiceResult != null) {
                Logger.e("Cannot popup the make choice dialog cause current already shown a 'mc' dialog.");
                return;
            }
    
            int windowWidth = (int) (ScannerApplication.getInstance().getHardware().getAppWidth() * 0.618f);
            int windowHeight = UnitManager.px2dp(123);
            Logger.d("dimension:" + windowWidth + "*" + windowHeight);
    
            // 1. make layout.
            GridLayout layout = new GridLayout(context);
            layout.setColumnCount(2);
            layout.setRowCount(2);
            layout.setBackground(context.getResources().getDrawable(R.drawable.round_corner_makechoice_dialog_bg));
    
            TextView tvContent = new TextView(context);
            tvContent.setText(content);
            tvContent.setTextSize(15);
            tvContent.setTextColor(context.getResources().getColor(R.color.gray_text_333));
            tvContent.setGravity(Gravity.CENTER);
            tvContent.setBackground(context.getResources().getDrawable(R.drawable.round_corner_makechoice_dialog_content_bg));
            GridLayout.LayoutParams glp = new GridLayout.LayoutParams(GridLayout.spec(0), GridLayout.spec(0, 2));
            glp.width = -1;
            glp.height = (int) (windowHeight * 0.6f);
            tvContent.setLayoutParams(glp);
    
            TextView tvCancel = new TextView(context);
            tvCancel.setTextColor(context.getResources().getColor(R.color.gray_text_888));
            tvCancel.setTextSize(15);
            tvCancel.setText(context.getText(R.string.no));
            tvCancel.setBackground(context.getResources().getDrawable(R.drawable.round_corner_makechoice_dialog_cancel_bg));
            tvCancel.setGravity(Gravity.CENTER);
            glp = new GridLayout.LayoutParams(GridLayout.spec(1), GridLayout.spec(0, 1.0f));
            if(Build.VERSION.SDK_INT <= 22){
                glp.width = (int) ((float) windowWidth / 2.0f);
            }
            glp.height = (int) (windowHeight * 0.4f);
            glp.topMargin = UnitManager.px2dp(1);
            tvCancel.setLayoutParams(glp);
            tvCancel.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Logger.d("cancel the make choice dialog");
                    if(onMakeChoiceResult != null) {
                        onMakeChoiceResult.onMakeChoice(false);
                        onMakeChoiceResult = null;
                    }
    
                    dismissDialog();
                    notifyStrongToastListener(false);
                }
            });
    
            TextView tvOk = new TextView(context);
            tvOk.setTextColor(context.getResources().getColor(R.color.toast_makechoice_txt_ok));
            tvOk.setTextSize(15);
            tvOk.setText(context.getText(R.string.yes));
            tvOk.setGravity(Gravity.CENTER);
            tvOk.setBackground(context.getResources().getDrawable(R.drawable.round_corner_makechoice_dialog_ok_bg));
            glp = new GridLayout.LayoutParams(GridLayout.spec(1), GridLayout.spec(1, 1.0f));
            if(Build.VERSION.SDK_INT <= 22){
                glp.width = (int) ((float) windowWidth / 2.0f) - UnitManager.px2dp(1);
            }
            glp.height = (int) (windowHeight * 0.4f);
            glp.topMargin = UnitManager.px2dp(1);
            glp.leftMargin = glp.topMargin;
            tvOk.setLayoutParams(glp);
            tvOk.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Logger.d("ok the make choice dialog");
                    if(onMakeChoiceResult != null) {
                        onMakeChoiceResult.onMakeChoice(true);
                        onMakeChoiceResult = null;
                    }
    
                    dismissDialog();
                    notifyStrongToastListener(false);
                }
            });
    
            layout.addView(tvContent);
            layout.addView(tvCancel);
            layout.addView(tvOk);
    
            // 2. decorate dialog and show it.
            if(dialog != null) {
                dialog.dismiss();
            }
            dialog = new AlertDialog.Builder(context).create();
            dialog.setCancelable(false);
            dialog.setCanceledOnTouchOutside(false);
            dialog.show();
            dialog.setContentView(layout); //Must behind on 'dialog.show()'.
    
            if(dialog.getWindow() != null) {
                dialog.getWindow().setLayout(windowWidth, windowHeight);
                dialog.getWindow().setBackgroundDrawable(new ColorDrawable(0));
            }
    
            onMakeChoiceResult = callback;
            notifyStrongToastListener(true);
        }
    
    
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    
        private void weakToast(String msg){
            if(Build.VERSION.SDK_INT > 25){
                toast = null;
                Toast toast = new Toast(context);
                toast.setDuration(Toast.LENGTH_SHORT);
                toast.setGravity(Gravity.CENTER, 0, UnitManager.px2dp(80));
                toast.setView(getTextView(msg));
                toast.show();
            }else{
                if(toast.getView() != null){
                    ((TextView)toast.getView()).setText(msg);
                }else{
                    toast.setView(getTextView(msg));
                }
                toast.show();
            }
        }
    
        private void strongToast(String msg){
            dismissDialog();
    
            dialog = new AlertDialog.Builder(context).create();
            dialog.setCanceledOnTouchOutside(false);
            dialog.setCancelable(false);
    
            dialog.show();
            dialog.setContentView(getDialogView(msg));
    
            if(dialog.getWindow() != null) {
                Logger.d("poping strong toast,screen:" +
                        ScannerApplication.getInstance().getHardware().getAppWidth() + "*" +
                        ScannerApplication.getInstance().getHardware().getAppHeight());
                dialog.getWindow().setLayout((int) (ScannerApplication.getInstance().getHardware().getAppWidth() * 0.618f), -2);
            }
    
            notifyStrongToastListener(true);
        }
    
        private void dismissDialog(){
            if(dialog != null) {
                dialog.dismiss();
                dialog = null;
            }
        }
    
        private TextView getTextView(String txt){
            TextView tv = new TextView(context);
            int padding = UnitManager.pix10();
            tv.setPadding(padding, padding, padding, padding);
            tv.setBackground(bgDrawable);
            tv.setGravity(Gravity.CENTER);
            tv.setTextColor(txtColor);
            tv.setTextSize(txtSize);
            tv.setText(txt);
    
            return tv;
        }
    
        private View getDialogView(String txt){
            final LinearLayout dialogLayout = new LinearLayout(context);
            dialogLayout.setGravity(Gravity.CENTER);
            dialogLayout.setBackground(context.getResources().getDrawable(R.drawable.round_corner_white_r5));
            dialogLayout.setOrientation(LinearLayout.VERTICAL);
            dialogLayout.setLayoutParams(new LinearLayout.LayoutParams(-1, -1));
    
            // 1. Information view.
            TextView tv = new TextView(context);
            tv.setPadding(UnitManager.pix10(), UnitManager.pix10(), UnitManager.pix10(), UnitManager.pix10());
            LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            llp.topMargin = UnitManager.px2dp(20);
            llp.bottomMargin = UnitManager.px2dp(20);
            tv.setLayoutParams(llp);
            tv.setGravity(Gravity.CENTER);
            tv.setTextColor(context.getResources().getColor(R.color.gray_textview_original));
            tv.setTextSize(12);
            tv.setText(txt);
    
            // 2. Divider line.
            View divider = new View(context);
            divider.setBackgroundColor(context.getResources().getColor(R.color.gray_background));
            llp = new LinearLayout.LayoutParams(-1, UnitManager.px2dp(2));
            divider.setLayoutParams(llp);
    
            // 3. Button.
            Button btn = new Button(context);
            btn.setText(R.string.ok);
            btn.setTextColor(context.getResources().getColor(R.color.basically_color));
            btn.setTextSize(16);
            btn.setBackground(context.getResources().getDrawable(R.drawable.round_corner_white_r5));
            btn.setLayoutParams(new LinearLayout.LayoutParams(-1, UnitManager.px2dp(40)));
            btn.setGravity(Gravity.CENTER);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    dismissDialog();
                    notifyStrongToastListener(false);
                }
            });
    
            dialogLayout.addView(tv);
            dialogLayout.addView(divider);
            dialogLayout.addView(btn);
    
            return dialogLayout;
        }
    
        public void showWaitingDialog(String info){
            dismissDialog();
    
            dialog = new AlertDialog.Builder(context).create();
            dialog.setCancelable(false);
            dialog.setCanceledOnTouchOutside(false);
            dialog.show();
            dialog.setContentView(getTextView(info));
    
            if(dialog.getWindow() != null) {
                dialog.getWindow().setLayout((int) (ScannerApplication.getInstance().getHardware().getAppWidth() * 0.382f), -2);
            }
        }
    
        public void dismissWaitingDialog(){
            dismissDialog();
        }
    
        private void notifyStrongToastListener(boolean isShown){
            if(onStrongToastListener != null) {
                onStrongToastListener.onStrongToastEvent(isShown);
                if(!isShown) {
                    onStrongToastListener = null; //一次性通知。
                }
            }
        }
    
        public void setOnStrongToastListener(OnStrongToastListener listener){
            onStrongToastListener = listener;
        }
    
        public interface OnMakeChoiceResult{
            void onMakeChoice(boolean yes);
        }
    
        public interface OnStrongToastListener {
            void onStrongToastEvent(boolean isShown);
        }
    }
    ToastManager源码

    它的使用方式也很简单,因为 Android 应用开发中不建议把 Context 静态保存(实际上对于 ToastManager 来说完全可以),而笔者不喜欢看到 Android Studio 的警告提示,就将 ToastManager 做成普通类的形式。同时,因为弹出 Dialog 需要 Activity 的 Context,因此,建议各位同学在 Activity 的初始化时创建 ToastManager 的实例。将实例以参数的形式传递给需要使用的地方即可。当然,其实最合理的方式是做成静态类的方式,这就需要同学自行去琢磨实现了。


  • 相关阅读:
    pycharm快捷键
    Java线程的生命周期
    Java中的管程
    Java并发编程之入门
    Linux系统监控命令
    RT-Thread 搜集一些其他博主的博客以备学习
    late_initcall 替换 module_init
    去掉行尾的^M
    ST3 C程序自动补全
    MinGW-W64 编译 LLVM 与 Clang
  • 原文地址:https://www.cnblogs.com/chorm590/p/11637795.html
Copyright © 2020-2023  润新知