在进行android开发时,我们经常用到Toast来给用于进行提示,大部分情况下android中提供给我们的默认方法基本上能够解决问题。但问题是有些用户嫌android原生的Toast不好看,想定义属于他们自己风格的Toast。考虑到别具一格的Toast确实能给用于耳目一新的感觉,因此我们也有了仔细去分析它实现细节的理由哈。先来看看demo中显示的android原生的Toast风格。

public void startToast(View view){        Toast.makeText(MainActivity.this, "您点击了Button按钮",         Toast.LENGTH_SHORT).show();}


为了一探究竟,我们进入makeText()方法中,我们来看看android中是怎样来实现该方法的:

//android官方的makeText方法public static Toast makeText(Context context, CharSequence text, @Duration int duration) {        Toast result = new Toast(context);        LayoutInflater inflate = (LayoutInflater)                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);        tv.setText(text);                result.mNextView = v;        result.mDuration = duration;        return result;}

从代码中可以看出,其实就是用LayoutInflater系统方法导入了一个android中内置的布局文件,我们再来看看这个原生的布局文件:

//com.android.internal.R.layout.transient_notification布局文件<?xml version="1.0" encoding="utf-8"?>    

com.android.internal.R.id.message就是这上面的TextView控件的ID。makeText方法中传入的text的参数赋值给了TextView。接着又把View赋值给了Toast的成员函数并返回了一个Toast类型的对象。说白了这里其实就是通过makeText方法构造出了一个Toast类型的对象,然后将android中内置的布局文件转成一个View对象以及显示的时间传给Toast的成员变量。

再来看看show()方法的实现原理。

//show()方法public void show() {        if (mNextView == null) {            throw new RuntimeException("setView must have been called");        }        INotificationManager service = getService();        String pkg = mContext.getOpPackageName();        TN tn = mTN;        tn.mNextView = mNextView;        try {            service.enqueueToast(pkg, tn, mDuration);        } catch (RemoteException e) {            // Empty        }}

在show方法中我们看TN类。这个方法中又把前面makeText方法中传递过来的View类型的对象给了TN类中的成员变量mNextView.貌似这个方法里也就做了这件事了。所以接下来我们要做的就是要去看看这个TN类具体长啥样。

 

原有的TN类有点长,这里我们删除了不影响我们理解的部分代码。

 //TN类 private static class TN extends ITransientNotification.Stub {               private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();        final Handler mHandler = new Handler();            int mGravity;        int mX, mY;        float mHorizontalMargin;        float mVerticalMargin;        View mView;        View mNextView;        WindowManager mWM;        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.format = PixelFormat.TRANSLUCENT;            params.windowAnimations = com.android.internal.R.style.Animation_Toast;            params.type = WindowManager.LayoutParams.TYPE_TOAST;            params.setTitle("Toast");            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;        }               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;                Context context = mView.getContext().getApplicationContext();                String packageName = mView.getContext().getOpPackageName();                if (context == null) {                    context = mView.getContext();                }                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);                // We can resolve the Gravity here by using the Locale for getting                // the layout direction                final Configuration config = mView.getContext().getResources().getConfiguration();                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());                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;                mParams.packageName = packageName;                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();            }        }        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;            }        }    }

首先,TN构造函数中设置了Toast的一些属性FLAG_NOT_FOCUSABLE  FLAG_NOT_TOUCHABLE等表明Toast没有交点  以及没有Touch事件。接下来在handleShow()方法中我们注意到mWM是一个WindowManager类型的窗口管理者,为了在手机屏幕上显示出Toast,只需要将我们传进来的mNextView加到mWM中就行了。所以就有了下面的

mWM.addView(mView, mParams);  这里第二个参数定义了我们Toast摆放的位置等等。哦,原来把我们的View对象加入到WindowManager类型的管理器中就行了啊。罗里吧嗦说了那么多,咱们赶紧来试试吧。

//activity_main.xml    

//toast.view.xml<?xml version="1.0" encoding="utf-8"?>        


    //button按钮的点击事件    public void startToast(View view){        //Toast.makeText(MainActivity.this, "您点击了Button按钮", Toast.LENGTH_SHORT).show();        toastMessage("您点击了Button按钮");    }         /**     * 该方法用于显示我们的Toast     * @param content:显示的具体内容     */    public void toastMessage(String content){        LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);        view = inflater.inflate(R.layout.toast_view, null);        TextView tv = (TextView) view.findViewById(R.id.tv_address);        tv.setText(content);        view.setBackgroundResource(R.drawable.green);        wm = (WindowManager) getSystemService(WINDOW_SERVICE);        WindowManager.LayoutParams params = new WindowManager.LayoutParams();        params.height = WindowManager.LayoutParams.WRAP_CONTENT;        params.width = WindowManager.LayoutParams.WRAP_CONTENT;        params.format = PixelFormat.TRANSLUCENT;        params.type = WindowManager.LayoutParams.TYPE_TOAST;        params.setTitle("Toast");        params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;        wm.addView(view,params);   }}

(很丑。。。。但是至少实现了我们想要的效果哈)

这里在布局文件中设置了一个Button按钮,并给它设置了一个点击事件。这里我们用toastMessage方法封装类似Toast.makeText().show()的功能。我们用LayoutInflate引入了我们自定义的布局文件R.layout.toast_view。在该文件中定义了我们想要显示的样式。将它加入到窗口管理器中就可以得到我们自定义的Toast啦。但是此时我们应该还来不及庆祝,应该大家将这个代码跑一遍就知道Toast是显示出来了,但是貌似是一直显示在那   除非我们把该应用杀死,他才会在手机上消失呢!

        

怎么解决这个问题呢?我们注意到我们在上面分析源码时handleHide()方法中有一个mWM.removeView(mView);  看了前面的mWM.addView()就应该知道这其实就是移除我们的Toast了。所以,这里我们可以设置在子线程中设置过了一定的时间就自动的移除它,当然具体的场景中显示的肯定不一样。比如在打电话显示号码归属地时,打电话时可以显示出来,等到电话挂断时就可以移除它了。我们这里用线程设置过了5秒就自动移除它。

package com.mobilesafe.yuwei.toast;import android.app.ActionBar;import android.graphics.PixelFormat;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.support.v7.app.ActionBarActivity;import android.view.LayoutInflater;import android.view.View;import android.view.WindowManager;import android.widget.TextView;public class MainActivity extends ActionBarActivity {    private WindowManager wm = null;    private View view = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ActionBar actionBar = getActionBar();        if (actionBar != null) {            actionBar.hide();        }    }    public void startToast(View view) {//        Toast.makeText(MainActivity.this, "您点击了Button按钮", Toast.LENGTH_SHORT).show();        toastMessage("您点击了Button按钮");    }    /**     * 该方法用于显示我们的Toast     * @param content:显示的具体内容     */    public void toastMessage(String content) {        LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);        view = inflater.inflate(R.layout.toast_view, null);        TextView tv = (TextView) view.findViewById(R.id.tv_address);        tv.setText(content);        view.setBackgroundResource(R.drawable.green);        wm = (WindowManager) getSystemService(WINDOW_SERVICE);        WindowManager.LayoutParams params = new WindowManager.LayoutParams();        params.height = WindowManager.LayoutParams.WRAP_CONTENT;        params.width = WindowManager.LayoutParams.WRAP_CONTENT;        params.format = PixelFormat.TRANSLUCENT;        params.type = WindowManager.LayoutParams.TYPE_TOAST;        params.setTitle("Toast");        params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;        wm.addView(view, params);        new Thread(new Runnable() {            @Override            public void run() {                try {                    Thread.sleep(5000);                    Message msg = handler.obtainMessage();                    msg.what = 1;                    handler.sendMessage(msg);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }).start();    }    Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            if (1 == msg.what) {                wm.removeView(view);                view = null;                wm = null;            }        }    };}

成功啦!这里要注意的是子线程是不能更新android中的UI控件的,所以需要定义一个handler来移除我们的Toast,当然,为了做的更逼真一点,大家可以自己设置Toast显示的时间哈,这个就比较简单了。


下篇文章中我们将一块学习可以自由移动的自定义Toast的实现原理

更多相关文章

  1. HTML5,js与Android(安卓)native通信
  2. android usb主从设备模式解析,网络连接调试方法
  3. Android的UI显示原理总结
  4. Android在网络中与JavaWeb的项目进行交互的方法
  5. Android(安卓)Handler
  6. Android、iPhone和Java三个平台一致的加密工具
  7. 如何看待 Kotlin 成为 Android(安卓)官方支持开发语言?
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. Android(安卓)Interface Definition Lang
  2. Android(安卓)PopWindow使用
  3. Android日期时间格式国际化
  4. android
  5. Android(安卓)Launcher3一些默认修改
  6. 安卓开发模拟器运行时报错原因以及解决方
  7. 迁移到Android(安卓)Studio 3.0
  8. delphi xe5 android 控制蓝牙
  9. android web services
  10. TextView字体逐渐变淡点以及程序特定list