有关 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 的实例。将实例以参数的形式传递给需要使用的地方即可。当然,其实最合理的方式是做成静态类的方式,这就需要同学自行去琢磨实现了。

 


 

posted @ 2020-02-03 16:53  大窟窿  阅读(1677)  评论(0编辑  收藏  举报