Android实例——全局Toast

BaseGlobalDialog

BaseGlobalDialog通过WindowManager添加View

public abstract class BaseGlobalDialog implements DefaultLifecycleObserver {
    private static final String TAG = "BaseGlobalDialog";
    protected Context mContext;
    protected ViewGroup root;
    protected TextView textView;
    private WindowManager mWindowManager = null;
    private boolean mIsShowing = false;
    protected boolean isDismissByTouchOutside = true;

    public BaseGlobalDialog(@NonNull Context context) {
        this.mContext = context;
        this.root = (ViewGroup) LayoutInflater.from(context).inflate(getLayoutId(), null);
        this.textView = root.findViewById(getTextViewId());
    }

    public void showToast(String text) {
        Log.d(TAG, "showDialog: ");
        if (mIsShowing) {
            Log.d(TAG, "showDialog: but is showing.");
            return;
        }
        mIsShowing = true;
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        textView.setText(text);
        mWindowManager.addView(root, getLayoutParams());
        setCancelTouchEvent();
        if (mContext instanceof AppCompatActivity) {
            ((AppCompatActivity) mContext).getLifecycle().addObserver(this);
        }
        Log.d(TAG, "showDialog: end");
    }

    public void setDismissByTouchOutside(boolean dismissByTouchOutside) {
        isDismissByTouchOutside = dismissByTouchOutside;
    }

    public abstract WindowManager.LayoutParams getLayoutParams();

    public abstract int getLayoutId();

    public abstract int getTextViewId();

    public boolean isShowing() {
        Log.d(TAG, "isShowing: mIsShowing = " + mIsShowing);
        return mIsShowing;
    }

    public void hideToast() {
        Log.d(TAG, "hideDialog: ");
        if (mIsShowing && mWindowManager != null) {
            mWindowManager.removeView(root);
            mIsShowing = false;
            if (mContext instanceof AppCompatActivity) {
                ((AppCompatActivity) mContext).getLifecycle().removeObserver(this);
            }
        }
        Log.d(TAG, "hideDialog: end");
    }

    public void hideToastImmediately() {
        Log.d(TAG, "hideToastImmediately: ");
        if (mIsShowing && mWindowManager != null) {
            mWindowManager.removeViewImmediate(root);
            mIsShowing = false;
            if (mContext instanceof AppCompatActivity) {
                ((AppCompatActivity) mContext).getLifecycle().removeObserver(this);
            }
        }
        Log.d(TAG, "hideToastImmediately: end");
    }

    @SuppressLint("ClickableViewAccessibility")
    private void setCancelTouchEvent() {
        root.setOnTouchListener((View v, MotionEvent event) -> {
            if (!isDismissByTouchOutside) {
                return true;
            }
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                int x = (int) event.getX();
                int y = (int) event.getY();
                Rect rect = new Rect();
                textView.getGlobalVisibleRect(rect);
                if (!rect.contains(x, y)) {
                    Log.d(TAG, "onTouch: outside");
                    hideToast();
                }
            }
            return false;
        });
        root.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                Log.d(TAG, "onTouch: keycode = " + keyCode);
                hideToast();
                return true;
            }
            return false;
        });
    }

    @Override
    public void onStop(@NonNull LifecycleOwner owner) {
        hideToast();
    }
}

AnimGlobalToast

AnimGlobalToast在新增View时加上动画效果

class AnimGlobalToast extends BaseGlobalDialog {

    private static final String TAG = "AnimGlobalToast";
    private static final Handler handler = new Handler(Looper.getMainLooper());
    private AnimatorSet mToastAnimatorSet;
    private boolean mIsHiding;

    public AnimGlobalToast(@NonNull Context mCtx) {
        super(mCtx);
    }

    @Override
    public WindowManager.LayoutParams getLayoutParams() {
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        params.format = PixelFormat.RGBA_8888;
        params.width = WindowManager.LayoutParams.MATCH_PARENT;
        params.height = WindowManager.LayoutParams.MATCH_PARENT;
        params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
        return params;
    }

    @Override
    public int getLayoutId() {
        return R.layout.layout_global_toast_normal;
    }

    @Override
    public int getTextViewId() {
        return R.id.toast_text;
    }

    public void showDialogNoAutoHide(String text, long millisecond) {
        showToast(text, millisecond, false);
    }

    public void showToast(String text, long millisecond) {
        showToast(text, millisecond, true);
    }

    private void showToast(String text, long millisecond, boolean isNeedHide) {
        startToastShowAnim(root, new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                AnimGlobalToast.super.showToast(text);
            }
        });
        mIsHiding = false;
        if (isNeedHide) {
            handler.postDelayed(this::hideToast, millisecond);
        }
    }

    private void startToastShowAnim(View view, AnimatorListenerAdapter listenerAdapter) {
        Log.d(TAG, "startToastShowAnim: ");
        int duation = 240;
        ArrayList<Animator> animatorArrayList = new ArrayList<>();
        animatorArrayList.add(ObjectAnimator.ofFloat(view, "translationY", 0, 30));
        animatorArrayList.add(ObjectAnimator.ofFloat(view, "alpha", 0f, 1f));
        if (mToastAnimatorSet != null && mToastAnimatorSet.isRunning()) {
            mToastAnimatorSet.cancel();
        }
        mToastAnimatorSet = new AnimatorSet();
        mToastAnimatorSet.setDuration(duation);
        mToastAnimatorSet.playTogether(animatorArrayList);
        mToastAnimatorSet.addListener(listenerAdapter);
        mToastAnimatorSet.start();
    }

    @Override
    public void hideToast() {
        if (mIsHiding) {    // 若动画已经启动,再调用hideToast()会闪一下
            Log.d(TAG, "hideToast: mIsHiding");
            return;
        }
        startToastHideAnim(textView, new AnimatorListenerAdapter() {

            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                mIsHiding = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                AnimGlobalToast.super.hideToast();
            }
        });
    }

    @Override
    public void hideToastImmediately() {
        AnimGlobalToast.super.hideToastImmediately();
    }

    private void startToastHideAnim(View view, AnimatorListenerAdapter listenerAdapter) {
        Log.d(TAG, "startToastHideAnim: ");
        int duation = 240;
        ArrayList<Animator> animatorArrayList = new ArrayList<>();
        animatorArrayList.add(ObjectAnimator.ofFloat(view, "translationY", -30, 0));
        animatorArrayList.add(ObjectAnimator.ofFloat(view, "alpha", 1f, 0f));
        if (mToastAnimatorSet != null && mToastAnimatorSet.isRunning()) {
            mToastAnimatorSet.cancel();
        }
        mToastAnimatorSet = new AnimatorSet();
        mToastAnimatorSet.setDuration(duation);
        mToastAnimatorSet.playTogether(animatorArrayList);
        mToastAnimatorSet.addListener(listenerAdapter);
        mToastAnimatorSet.start();
    }
}

GlobalToastManager

GlobalToastManager用单例控制AnimGlobalToast生命周期

public class GlobalToastManager {
    private BaseGlobalDialog currentToast;
    public static final long DEFAULT_TOAST_TIME = 2000;

    private static class Holder {
        private static final GlobalToastManager INSTANCE = new GlobalToastManager();
    }

    private GlobalToastManager() {
    }

    public static GlobalToastManager getInstance() {
        return Holder.INSTANCE;
    }

    public void showToast(Context context, String msg) {
        showToast(context, msg, true, DEFAULT_TOAST_TIME);
    }

    public void showToast(Context context, String msg, boolean dismissByTouchOutside) {
        showToast(context, msg, dismissByTouchOutside, DEFAULT_TOAST_TIME);
    }

    public void showToast(Context context, String msg, boolean dismissByTouchOutside, long time) {
        if (currentToast != null) {
            currentToast.hideToastImmediately();
        }
        AnimGlobalToast nextToast = new AnimGlobalToast(context);
        nextToast.setDismissByTouchOutside(dismissByTouchOutside);
        nextToast.showToast(msg, time);
        currentToast = nextToast;
    }

    public void showToastNoAutoHide(Context context, String msg) {
        showToastNoAutoHide(context, msg, true, DEFAULT_TOAST_TIME);
    }

    public void showToastNoAutoHide(Context context, String msg, boolean dismissByTouchOutside) {
        showToastNoAutoHide(context, msg, dismissByTouchOutside, DEFAULT_TOAST_TIME);
    }

    public void showToastNoAutoHide(Context context, String msg, boolean dismissByTouchOutside, long time) {
        if (currentToast != null) {
            currentToast.hideToastImmediately();
        }
        AnimGlobalToast nextToast = new AnimGlobalToast(context);
        nextToast.setDismissByTouchOutside(dismissByTouchOutside);
        nextToast.showDialogNoAutoHide(msg, time);
        currentToast = nextToast;
    }

    public void hideToast() {
        if (currentToast != null) {
            BaseGlobalDialog hideToast = currentToast;
            currentToast = null;
            hideToast.hideToast();
        }
    }
}

使用方式

声明权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

使用时需要申请权限

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private ImageView mIv1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mIv1 = findViewById(R.id.iv1);
        mIv2 = findViewById(R.id.iv2);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            startActivity(intent);
        }

        mIv1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "onClick: mIv1");
                GlobalToastManager.getInstance().showToast(MainActivity.this, "hellohellohellohellohellohellohello");
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        GlobalToastManager.getInstance().hideToast();
                    }
                }, 1000);
            }
        });
    }
}
### 如何在 Android 中对 Toast 进行非空判断 在 Android 开发中,`Toast` 是一种用于显示短暂消息的方式。然而,由于 `Toast` 的设计特性,在某些情况下可能会遇到重复创建或覆盖的情况。为了防止这种情况发生并确保 `Toast` 不为空或者不会多次弹出相同的消息,可以通过以下方式进行处理。 #### 方法一:通过全局变量管理 `Toast` 为了避免多个 `Toast` 同时存在,可以在应用中维护一个全局的 `Toast` 对象,并在其每次展示前先取消之前的实例再重新设置新内容。 以下是具体实现: ```java public class MainActivity extends AppCompatActivity { private Toast toast; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = findViewById(R.id.button); button.setOnClickListener(v -> showCustomToast("This is a test message")); } public void showCustomToast(String message) { if (toast != null) { toast.cancel(); // 取消当前存在的 Toast 实例 } if (message != null && !message.isEmpty()) { // 非空判断 toast = Toast.makeText(this, message, Toast.LENGTH_SHORT); toast.show(); } else { Log.e("MainActivity", "Message cannot be empty"); } } } ``` 上述代码实现了对 `Toast` 的非空验证以及避免重复弹窗的功能[^4]。 #### 方法二:封装自定义工具类 如果项目中有大量地方需要用到类似的逻辑,则可以考虑将其抽象成一个通用方法供其他模块调用。 下面是基于静态方法的一个简单例子: ```java public class ToastUtils { private static Toast currentToast; /** * 显示带有非空校验功能的 Toast 提示框。 * * @param context 上下文对象 * @param text 待显示的文字信息 */ public static void showToast(Context context, String text) { if (context == null || TextUtils.isEmpty(text)) { return; } if (currentToast != null) { currentToast.cancel(); // 如果已有正在运行中的 Toast 则立即停止它 } currentToast = Toast.makeText(context.getApplicationContext(), text, Toast.LENGTH_SHORT); currentToast.show(); } } ``` 此版本不仅包含了基本的安全性检测还提供了更灵活的应用场景支持[^5]。 --- ### 总结 无论是采用直接操作还是借助辅助函数的形式都可以有效解决因缺乏必要检查而导致用户体验下降的问题。以上两种方案均能很好地满足需求——即只允许有效的字符串作为输入参数传入至最终呈现给用户的界面组件之中去执行相应的行为动作序列化过程。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值