您的位置:首页 > 移动开发 > Android开发

Android中Toast使用总结,源码剖析

2015-05-21 20:19 302 查看
最近看到折800刷新成功弹出的效果挺好看的,就想着自己也实现一下,下面是折800的刷新效果



感觉这个可以用popupwindow或者toast来实现,就用toast来实现了。布局比较简单,不贴图了,直接贴上代码:
public void showToast() {

        LayoutInflater inflater = LayoutInflater.from(BaseApplication.get());
        View layout = inflater.inflate(R.layout.toast_refresh_layout, null);
        TextView textView = (TextView)layout.findViewById(R.id.tvrefresh);
        textView.setText(getResources().getString(R.string.refresh));

        Toast toast = new Toast(mContext);
        toast.setDuration(Toast.LENGTH_SHORT);
        toast.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL, 0
                , UnitUtils.dip2pix(mContext,50));
        toast.setView(layout);
        toast.show();
    }


 

有必要对Toast做一下总结了,平时用到toast的时候基本都是

Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();


简单看了下源码:

public static Toast makeText(Context context, CharSequence text, 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;
    }


代码很简单,就是inflater一个view,transient_notification这个layout中只有一个textview,所以平时弹出的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
        }
    }


看到getservice方法,很明显这是一个系统服务,下面追一下:

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


写过aidl的应该都知道,这是就是实现一个InotificationManager的代理类,相当于binder中的bpserver,binder的详细讲解可以参考罗升阳的csdn博客。这里也就很清晰了,toast其实和notification通知是一家的,只不过显示形式不同罢了。TN是INotificationManager的实现类:

private static class TN extends ITransientNotification.Stub


抽取一些关键的代码:

//设定位置,默认值就是这个,所以默认的toast是居下的
int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;


在代码中可以通过设置gravity来控制toast的位置:

Toast toast = Toast.makeText(this, R.string.custom_text, Toast.LENGTH_SHORT);  
//toast显示到顶部,靠左显示
toast.setGravity(Gravity.TOP|Gravity.LEFT, 0, 100);  
toast.show();


setGravity这个方法设置toast将要显示的屏幕位置,这个方法传参的含义是:

第一个参数,设定Toast显示位置的锚点,就是Toast这个视图的初始位置。

第二个参数,设定Toast从锚点开始,在屏幕坐标X轴上的偏移量,向右是正数,向左是负数。

第三个参数,设定Toast从锚点开始,在屏幕坐标Y轴上的偏移量,向下时正数,向上市负数。

代码内,我们设置它的锚点是屏幕的左上角,X轴方向偏移为0,Y轴方向,向下偏移100。我们来看看具体的效果图:





接着看源码,toast的layout的参数设置:

final WindowManager.LayoutParams params = mParams;
          //下面是高和宽,都是wrap,怪不得我在xml自定义toast布局fill还是充满不了
            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;
         //这里是toast的动画效果
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
//type就是toast类型的,很多悬浮框都是更改的这个type,一般悬浮框都是TYPE_PHONE或者TYPE_SYSTEM_ALERT
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");


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();
//设置自定义view的地方,通过setview给mNextView赋值
                mView = mNextView;
                mWM = WindowManagerImpl.getDefault();
                final int gravity = mGravity;
                mParams.gravity = gravity;
//下面的是重点,原来gravity设置成FILL_HORIZONTAL才会充满横屏
                if ((gravity & Gravity.HORIZONTAL_GR***ITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = 1.0f;
                }
                if ((gravity & Gravity.VERTICAL_GR***ITY_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();
            }
        }


上面是添加View的代码,比较简单,看一下就行了,而且还有自定义Toast的View的功能。

自定义Toast视图,不用makeText,直接获取Toast对象,设置view界面。

LayoutInflater inflater = getLayoutInflater();  
View layout = inflater.inflate(R.layout.custom_toast,null);  
Toast toast = new Toast(getApplicationContext());  
toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);  
toast.setDuration(Toast.LENGTH_LONG);  
toast.setView(layout);  
toast.show();


说明一下,Toast的show和hide方法实现是基于Handler机制,直接new的Handler对象,所以,当我们在主线程(也就是UI线程中)可以随意调用Toast.makeText方法,因为Android系统帮我们实现了主线程的Looper初始化。但是,如果你想在子线程中调用Toast.makeText方法,就必须先进行Looper初始化了,不然就会报出java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 。

public Handler() {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

//默认调用当前线程的looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = null;
    }


最后说明一点,Toast的动画效果,也就是上面设置的默认com.android.internal.R.style.Animation_Toast,如果需要更改动画,我没有找到比较好的版本,暂时只能自己写一个自定义的Toast来代替,源码地址:

https://github.com/xuwt/CustomerToast

Toast源码解析参考:/article/1644059.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: