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

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

android中自定义Toast方法详解(一)_第1张图片

为了一探究竟,我们进入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);   }}
android中自定义Toast方法详解(一)_第2张图片
(很丑。。。。但是至少实现了我们想要的效果哈)

这里在布局文件中设置了一个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. Android 文件管理方法
  2. android:布局参数,控件属性及各种xml的作用
  3. Android在网络中与JavaWeb的项目进行交互的方法
  4. Android加载Gif图片的一般方法:Movie实现
  5. Android布局案例之人人android九宫格
  6. Android、iPhone和Java三个平台一致的加密方法
  7. Android中布局的巧妙设计【android进化二十六】

随机推荐

  1. Android开发从零开始视频教程
  2. Android应用程序与SurfaceFlinger服务的
  3. Android(安卓)App开发基础篇—Android(安
  4. 浅谈android的selector,背景选择器
  5. Android核心模块及相关技术
  6. Mac的android真机调试
  7. 第一讲:Android开发环境的搭建
  8. Android(安卓)更改软键盘Enter键为搜索
  9. Maven开发Android指南 1 简介
  10. android java代码的启动:app_process