Android原理——自定义Toast原理

自定义Toast原理

概要

1. 使用Toast遇到的问题

2. Toast源码及原理

3. 我的单例类 T.java

使用Toast遇到的问题

  • 原生的Toast真的很难看不是吗
  • 多个Toast依次显示,程序都结束了还在不停的显示呢

    解决办法:自定义Toast + 单例类


Toast源码及原理

Toast的源码不多,只有423行

有些我们常用的方法,想必不用多说,例如:

public Toast(Context context)

public void show()

public void cancel()

public static Toast makeText(Context context, int resId, int duration)

public static Toast makeText(Context context, CharSequence text, int duration)

重要的是:

有一个内部类TN(其实是一个Binder),是TransientNotification的缩写 短暂的通知

private static class TN extends ITransientNotification.Stub {

        final Runnable mShow = new Runnable() {
            public void run() {
                handleShow();
            }
        };

        final Runnable mHide = new Runnable() {
            public void run() {
                handleHide();
                // Don‘t do this in handleHide() because it is also invoked by handleShow()
                mNextView = null;
            }
        };

        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        final Handler mHandler = new Handler();    

        int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
        int mX, mY;
        float mHorizontalMargin;
        float mVerticalMargin;

        /**
         * @me: mNextView是Toast要显示的View
         */
        View mView;
        View mNextView;

        WindowManagerImpl mWM;

        /**
         * @me: 这里有些值得注意的 params.flags
         * 使其不可点击,不可获取焦点,保证屏幕高亮
         * 窗口类型为WindowManager.LayoutParams.TYPE_TOAST
         */
        TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
        }

        /**
         * schedule handleShow into the right thread
         */
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }

        /**
         * schedule handleHide into the right thread
         */
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.post(mHide);
        }

        public void handleShow() {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                mWM = WindowManagerImpl.getDefault();
                final int gravity = mGravity;
                mParams.gravity = gravity;
                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = 1.0f;
                }
                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                    mParams.verticalWeight = 1.0f;
                }
                mParams.x = mX;
                mParams.y = mY;
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                mWM.addView(mView, mParams);
                trySendAccessibilityEvent();
            }
        }

        private void trySendAccessibilityEvent() {
            AccessibilityManager accessibilityManager =
                    AccessibilityManager.getInstance(mView.getContext());
            if (!accessibilityManager.isEnabled()) {
                return;
            }
            // treat toasts as notifications since they are used to
            // announce a transient piece of information to the user
            AccessibilityEvent event = AccessibilityEvent.obtain(
                    AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
            event.setClassName(getClass().getName());
            event.setPackageName(mView.getContext().getPackageName());
            mView.dispatchPopulateAccessibilityEvent(event);
            accessibilityManager.sendAccessibilityEvent(event);
        }        

        public void handleHide() {
            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
            if (mView != null) {
                // note: checking parent() just to make sure the view has
                // been added...  i have seen cases where we get here when
                // the view isn‘t yet added, so let‘s try not to crash.
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                mView = null;
            }
        }
    }

重要的还有这个:

Toast中的mService的调用,其实通过IPC调用到了NotificationManagerService

private static INotificationManager sService;

    static private INotificationManager getService() {
        if (sService != null) {
            return sService;
        }
        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
        return sService;
    }

在关键的Toast show()方法中:

public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

INotificationManager 把消息交给 Notification 去处理 Toast 显示, 消息全部是放在一个集合中,

是这么定义的 private ArrayList<ToastRecord> mToastQueue;

这就为什么多个Toast会一直逐个显示,直到没有消息为止.


我的单例类 T.java

举个自定义Toast的例子:

   LayoutInflater inflater = getLayoutInflater();
   View layout = inflater.inflate(R.layout.custom,
     (ViewGroup) findViewById(R.id.llToast));

   ImageView image = (ImageView) layout
     .findViewById(R.id.tvImageToast);
   image.setImageResource(R.drawable.icon);
   TextView title = (TextView) layout.findViewById(R.id.tvTitleToast);
   title.setText("Attention");
   TextView text = (TextView) layout.findViewById(R.id.tvTextToast);
   text.setText("自定义Toast");

   toast = new Toast(getApplicationContext());
   toast.setGravity(Gravity.RIGHT | Gravity.TOP, 12, 40);
   toast.setDuration(Toast.LENGTH_LONG);
   toast.setView(layout);
   toast.show();

你可以这么做,但我不打算这么做

工具类是一个独立的个体,尽量减少对其他资源的依赖

所以我这里使用代码布局来生成界面

/**
 * Toast工具类
 *
 * 单例类
 */
public class T {

    private static Toast toast = null;

    private static LinearLayout toastView = null;

    private static final int textId = 2345;

    /**
     * Toast.LENGTH_LONG
     */
    public static void l(String msg) {

        createToast(LianApplication.getInstance());

        toast.setDuration(Toast.LENGTH_LONG);
        setText(msg);
        toast.show();

    }

    /**
     * Toast.LENGTH_SHORT
     */
    public static void s(String msg) {

        createToast(LianApplication.getInstance());

        toast.setDuration(Toast.LENGTH_SHORT);
        setText(msg);
        toast.show();

    }

    private static void setText(String msg) {

        TextView tv = (TextView) toastView.findViewById(textId);
        if (tv != null) {
            tv.setText(msg);
        }
    }

    private static void createToast(Context context) {

        if (toastView == null) {
            toastView = new LinearLayout(context);
            toastView.setOrientation(LinearLayout.VERTICAL);
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.WRAP_CONTENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT);
            toastView.setLayoutParams(layoutParams);
            toastView.setBackgroundColor(Color.GRAY);

            TextView textView = new TextView(context);
            textView.setText("");
            textView.setPadding(10, 10, 10, 10);
            textView.setTextColor(Color.WHITE);
            textView.setLayoutParams(layoutParams);
            // 注意设置ID
            textView.setId(textId);

            toastView.addView(textView);
        }

        if (toast == null) {
            toast = new Toast(context);
            toast.setGravity(Gravity.CENTER, 0, 0);
            toast.setView(toastView);
        }
    }
}
时间: 2024-10-04 09:11:02

Android原理——自定义Toast原理的相关文章

Android 高级自定义Toast及源码解析

Toast概述 Toast的作用 不需要和用户交互的提示框. 更多参见官网:https://developer.android.com/guide/topics/ui/notifiers/toasts.html Toast的简单使用 Toast.makeText(MainActivity.this.getApplicationContext(),"沉迷学习,日渐消瘦",Toast.LENGTH_SHORT).show() 自定义Toast Toast customToast = new

Android Toast进阶——自定义Toast

进阶目标 上一篇博客我们学习了Toast的源码,了解了Toast从显示到消失的全过程,学习链接:Android Toast源码分析 .俗话说的好,学以致用.我们学习Toast源码不是用来炫技的,而是用来了解Toast原理,从而真正解决我们问题的.下面我就提两个业务中可能遇到的跟Toast相关的真实问题,看看学习了Toast源码之后,该如何解决这些问题.两个问题是: 如何自定义Toast的显示时间. 如何修改Toast的出现动画. 接下来,我们分别讲解阅读了Toast源码之后,如何解决这两个业务中

Android 长截屏原理

https://android-notes.github.io/2016/12/03/android%E9%95%BF%E6%88%AA%E5%B1%8F%E5%8E%9F%E7%90%86/   android长截屏原理 小米系统自带的长截屏应该很多人都用过,效果不错.当长截屏时listview就会自动滚动,当按下停止截屏时,就会得到一张完整的截屏. 该篇就介绍一下长截屏的原理 上篇中介绍了android屏幕共享实现方式,该篇的原理和上一篇基本一致. 获取view影像 当我们想得到一个view

自定义View基础 - 最易懂的自定义View原理系列(1)

前言 自定义View原理是Android开发者必须了解的基础: 在了解自定义View之前,你需要有一定的知识储备: 本文将全面解析关于自定义View中的所有知识基础. 目录 1. View的分类 视图View主要分为两类: 类别 解释 特点 单一视图 即一个View,如TextView 不包含子View 视图组 即多个View组成的ViewGroup,如LinearLayout 包含子View 2. View类简介 View类是Android中各种组件的基类,如View是ViewGroup基类

Android FoldingLayout 折叠布局 原理及实现(二)

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/44283093,本文出自:[张鸿洋的博客] 1.概述 在上一篇Android FoldingLayout 折叠布局 原理及实现(一)我们实现了下面的1,2,3. 1.Matrix的setPolyToPoly使用 2.在图片上使用渐变和阴影 3.初步的FoldingLayout的实现,完成图片的折叠显示(可控制折叠次数.包含阴影的绘制) 4.引入手势,手指可以可以FoldingLa

Android带图片的Toast(自定义Toast)

使用Android默认的Toast Toast简介: Toast是一个简单的消息显示框,能够短暂的出现在屏幕的某个位置,显示提示消息. 默认的位置是屏幕的下方正中,一般Toast的使用如下: Toast.makeText(this,"1222222",Toast.LENGTH_SHORT).show(); Toast是static修饰的静态类,意味着可以直接使用,所以可以不用创建对象直接调用makeText方法, 该方法需要传入三个参数: /** * Make a standard t

Android自复制传播APP原理学习(翻译)

 Android自复制传播APP原理学习(翻译) 1 背景介绍 论文链接:http://arxiv.org/abs/1511.00444 项目地址:https://github.com/Tribler/self-compile-Android 吃完晚饭偶然看到这篇论文,当时就被吸引了,马上翻译总结了一下.如有错误欢迎斧正. 该论文的研究出发点比较高大上这里我们就不多说了,简而言之就是想通过移动设备来实现一个自组网,在发生灾难的时候,手机之间能够自动传输关键数据,减少损失.整个目标通过设计一个能够

Android之十一Toast 自定义Toast的实现方法,及其说明

Android Toast 自定义Toast的实现方法,及其说明 Android Toast用于在手机屏幕上向用户显示一条信息,一段时间后信息会自动消失.信息可以是简单的文本,也可以是复杂的图片及其他内容(显示一个view).  1.简单用法 Toast.makeText(midlet.getApplicationContext(), "用户名不能为空", Toast.LENGTH_LONG).show(); 2.自定义显示位置效果 代码 toast = Toast.makeText(

android 自定义Toast显示风格

1.创建一个自己想要显示Toast风格的XML如下代码(toast_xml.xml): [html] view plaincopyprint? <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match