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 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(MyApplication.getInstance());

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

    }

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

        createToast(MyApplication.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);
        }
    }
}

你可能感兴趣的:(Android原理)