有關 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 的實例。將實例以參數的形式傳遞給需要使用的地方即可。當然,其實最合理的方式是做成靜態類的方式,這就需要同學自行去琢磨實現了。

 


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM