Android Toast的学习与简单应用

来源:互联网 发布:杰科网络电视机顶盒r1 编辑:程序博客网 时间:2024/04/30 13:26

关于Toast我们应该很是熟悉了,通过Toast.makeText(context ,text,duration).show() ;就可以很容易显示一个Toast。最近在写一个悬浮框的一个小功能,开始时

啥也不知道,根本无从下手。随后在网上搜了一大堆的资料,各有各的说法,不过大部分都是说添加一个权限(<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />) 并结合WindowManager来显示。这个我测试了一下好像不行(permission denied),当时用的是

Android 5.1 的系统。不知道是不是用法没用对,不管那么多了。最后在Toast身上找到了一线生机,现将学习笔记记录如下:


     在使用Toast时可以发现,Toast可以在所有View的最上面出现,也就是它不会被覆盖而一直在最顶层。现在知道了这个特性,那就开始从Toast的源码中去找我们的

答案了

     Toast.makeText(context ,text,duration).show() ;这句代码闭着眼都能写出来了吧

          先来看一看makeText()方法,源码如下:

       

   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);//将<span style="font-family: Arial, Helvetica, sans-serif;">com.android.internal.R.layout.transient_notification打成View</span>        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);        tv.setText(text);                result.mNextView = v;        result.mDuration = duration;        return result;    }
     其实上面的代码很简单,总结一下就是做了如下几件事:

1、将com.android.internal.R.layout.transient_notification制成view

2、在view找到一个TextView(布局文件里只有一个TextView),用来显示我们传进来的text

3、将view赋值给mNextView ,将传进来的duration赋给成员变量。最后返回一个Toast实例result


接着看看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        }    }
获得了一个service,看到INotificationManager 这样的东西我就头痛,应该是调用了一个远程的接口,最后在try模块里使用service.enqueueToast(pkg, tn, mDuration);这是个啥呢,其实一开始我也不知道是什么东西,不过看单词enqueue应该可以猜到是加入了一个Toast队列之中。这么猜应该是对的,因为在我们点击

事件来显示Toast时,可以发现点击多少下就会显示几次Toast,而且都是显示完一个才会显示下一个。

好了,show方法就是获得一个远程接口并将Toast加入了一个队列。并没有看到显示的方法啊。别着急,还有一个TN。TN是又是个啥?找出源码看看不就知道了

   private static class TN extends ITransientNotification.Stub {        final Runnable mShow = new Runnable() {            @Override            public void run() {                handleShow();            }        };        final Runnable mHide = new Runnable() {            @Override            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;               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.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         */        @Override        public void show() {            if (localLOGV) Log.v(TAG, "SHOW: " + this);            mHandler.post(mShow);        }        /**         * schedule handleHide into the right thread         */        @Override        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;                Context context = mView.getContext().getApplicationContext();                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;                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();            }        }
<pre name="code" class="html">    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;            }        }


一开始看到也是头大得不行,都写的什么呀!一开头写了两个Runnable。也不知道是干什么用的,一个叫mShow,一个叫mHide。往下看在构造中初始化了一些

WindowManager.LayoutManager的参数。

现在开始注意一个叫handleShow的方法,看着就像是用来显示Toast的方法,其实也就是它了。在该方法里初始化了一个WindowManager了。主角终于来了!

中间都是一些参数的设置,不去管它,看看handleShow倒数第二行代码mWM.addView(mView,mParas).注意到handleShow方法的开头有一句mView = mNextView,mNextView不就是makeText()中的TextView么。OK,这里将mView添加进了mWM中了。这样我们的Toast就显示出来了。

再来看看handleHide,只是将mView从mWM中移除了,这样mView就消失了。

      注:ITransientNotification.Stub中有两个方法show()和hide().

     这里小结一下:

1、inflate一个布局,找到里面的TextView,并给它设置我们传进来的text

2、获取一个Toast队列,并将tn和duration加入到一个队里里面,排队去显示

3、TN中初始化了一些WindowManager.LayoutParams的一些参数,并创建了一个WindowManager对象,将mView添加到WindowManager中。

4、最后就是handHide,将View移除。

通过以上的步骤就完成了Toast的显示和消失。

到了这里有没有发现Toast显示奥妙了?其实很简单,就是先创建一个WindowManager,再配套一个WindowManager.LayoutParams。通过WindowManager.add

来显示一个View(这里的View可以自己随便写,最后inflate成View就OK,或者直接new),通过remove让view消失。

下面就来写一个可拖动的小Demo:

废话就不多说了,直接贴代码:

需要显示的View的布局

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:background="#000000" >    <com.exapmle.windowdemo.ScrollButton        android:id="@+id/btn"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="悬浮框"/> </LinearLayout>
ScrollButton的代码

public class ScrollButton extends Button {private int startX;private int startY;private int moveX;private int moveY;private boolean isClick ;private OnLocationChangeListener mOnLocationChangeListener;private Context context ;public ScrollButton(Context context) {this(context, null);}public ScrollButton(Context context, AttributeSet attrs) {super(context, attrs);this.context = context ;}public interface OnLocationChangeListener {void upDateLocation(int dx, int dy);}public void setFloatWindowChangeManager(OnLocationChangeListener listener) {mOnLocationChangeListener = listener;}@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:startX = (int) event.getRawX();startY = (int) event.getRawY();isClick = true ;break;case MotionEvent.ACTION_MOVE:moveX = (int) event.getRawX();moveY = (int) event.getRawY();if ((Math.abs(moveX - startX)) >= 10 || (Math.abs(moveY - startY) >= 10)) {mOnLocationChangeListener.upDateLocation(moveX - startX, moveY - startY);startX = moveX ;startY = moveY ;isClick = false ;}break;case MotionEvent.ACTION_UP:if (isClick) {Toast.makeText(context, "被点击了", Toast.LENGTH_SHORT).show();}break;}return true ;}}
这里将启动悬浮框的代码写在了service中

 public class FloatService extends Service implements OnLocationChangeListener {private WindowManager mManager;private WindowManager.LayoutParams mParams;private View view;private com.gemdale.myfloatwindowdemo.ScrollButton btn;@Overridepublic IBinder onBind(Intent intent) {return null;}@SuppressLint({ "InflateParams", "InlinedApi" })@Overridepublic void onCreate() {super.onCreate();addWindowView();}/** * windowManager.addView(view,params) 的显示周期与相应的context的存在相关。activity只能是该activity存在时view显示,销毁,view消失 */private void addWindowView() {mManager = (WindowManager) getApplicationContext().getSystemService(Application.WINDOW_SERVICE);mParams = new WindowManager.LayoutParams();mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;mParams.gravity=Gravity.TOP|Gravity.LEFT ;//使用TYPE_TOASTmParams.type = WindowManager.LayoutParams.TYPE_TOAST;//WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  不抢焦点,若不设置该flag,back事件无效//WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 手机屏幕打开时,改window一直显示//WindowManager.LayoutParams.FLAG_SPLIT_TOUCH  当该window在可以接受触摸屏情况下,让因在该window之外,而发送到后面的window的触摸屏可以支持split touch.//WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 即使在该window在可获得焦点情况下,仍然把该window之外的任何event发送到该window之后的其他window.mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_SPLIT_TO                UCH | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;initViewConfig() ;mManager.addView(view, mParams);}private void initViewConfig() {view = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.float, null);btn = (com.gemdale.myfloatwindowdemo.ScrollButton) view.findViewById(R.id.btn);btn.setFloatWindowChangeManager(this);}@Overridepublic void onDestroy() {mManager.removeView(view);mParams = null;mManager = null;super.onDestroy();}@Overridepublic void upDateLocation(int dx, int dy) {mParams.x += dx;mParams.y += dy;mManager.updateViewLayout(view, mParams);//不断的去更新mManager的位置}}

最后是MainActivity了,就两个button,

@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.open:Intent intent = new Intent(MainActivity.this, FloatService.class);startService(intent);break;case R.id.close:Intent intent = new Intent(MainActivity.this, FloatService.class);stopService(intent);break;}}
到了这里,一个简单的可显示可拖动的悬浮框就完成了。


此篇文章仅为个人的学习笔记,参考了很多大家贡献的文章。若有雷同请勿怪,若有错误请指正,谢谢!



















0 0