详解Android中的Toast源码

Toast源码实现
Toast入口
    我们在应用中使用Toast提示的时候,一般都是一行简单的代码调用,如下所示:
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

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

makeText就是Toast的入口,我们从makeText的源码来深入理解Toast的实现。源码如下(frameworks/base/core/java/android/widget/Toast.java):

  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;
  }

从makeText的源码里,我们可以看出Toast的布局文件是transient_notification.xml,位于frameworks/base/core/res/res/layout/transient_notification.xml:

  <?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="?android:attr/toastFrameBackground"> 

    <TextView
      android:id="@android:id/message"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:layout_gravity="center_horizontal"
      android:textAppearance="@style/TextAppearance.Toast"
      android:textColor="@color/bright_foreground_dark"
      android:shadowColor="#BB000000"
      android:shadowRadius="2.75"
      /> 

  </LinearLayout>

系统Toast的布局文件非常简单,就是在垂直布局的LinearLayout里放置了一个TextView。接下来,我们继续跟到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
    }
  }

show方法中有两点是需要我们注意的。(1)TN是什么东东?(2)INotificationManager服务的作用。带着这两个问题,继续我们Toast源码的探索。
TN源码
    很多问题都能通过阅读源码找到答案,关键在与你是否有与之匹配的耐心和坚持。mTN的实现在Toast的构造函数中,源码如下:

  public Toast(Context context) {
    mContext = context;
    mTN = new TN();
    mTN.mY = context.getResources().getDimensionPixelSize(
        com.android.internal.R.dimen.toast_y_offset);
    mTN.mGravity = context.getResources().getInteger(
        com.android.internal.R.integer.config_toastDefaultGravity);
  }

接下来,我们就从TN类的源码出发,探寻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;
    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;
      /// M: [ALPS00517576] Support multi-user
      params.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
    } 

    /**
     * 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();
      }
    } 

    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;
      }
    }
  }

通过源码,我们能很明显的看到继承关系,TN类继承自ITransientNotification.Stub,用于进程间通信。这里假设读者都有Android进程间通信的基础(不太熟的建议学习罗升阳关于Binder进程通信的一系列博客)。既然TN是用于进程间通信,那么我们很容易想到TN类的具体作用应该是Toast类的回调对象,其他进程通过调用TN类的具体对象来操作Toast的显示和消失。
    TN类继承自ITransientNotification.Stub,ITransientNotification.aidl位于frameworks/base/core/java/android/app/ITransientNotification.aidl,源码如下:

  package android.app; 

  /** @hide */
  oneway interface ITransientNotification {
    void show();
    void hide();
  }

ITransientNotification定义了两个方法show()和hide(),它们的具体实现就在TN类当中。TN类的实现为:

  /**
   * 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);
  }

这里我们就能知道,Toast的show和hide方法实现是基于Handler机制。而TN类中的Handler实现是:

  final Handler mHandler = new Handler();

而且,我们在TN类中没有发现任何Looper.perpare()和Looper.loop()方法。说明,mHandler调用的是当前所在线程的Looper对象。所以,当我们在主线程(也就是UI线程中)可以随意调用Toast.makeText方法,因为Android系统帮我们实现了主线程的Looper初始化。但是,如果你想在子线程中调用Toast.makeText方法,就必须先进行Looper初始化了,不然就会报出java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 。Handler机制的学习可以参考我之前写过的一篇博客:http://blog.csdn.net/wzy_1988/article/details/38346637。
    接下来,继续跟一下mShow和mHide的实现,它俩的类型都是Runnable。

  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;
    }
  };

可以看到,show和hide的真正实现分别是调用了handleShow()和handleHide()方法。我们先来看handleShow()的具体实现:

 public void handleShow() {
    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) {
        mWM.removeView(mView);
      }
      mWM.addView(mView, mParams);
      trySendAccessibilityEvent();
    }
  }

从源码中,我们知道Toast是通过WindowManager调用addView加载进来的。因此,hide方法自然是WindowManager调用removeView方法来将Toast视图移除。
    总结一下,通过对TN类的源码分析,我们知道了TN类是回调对象,其他进程调用tn类的show和hide方法来控制这个Toast的显示和消失。
NotificationManagerService
    回到Toast类的show方法中,我们可以看到,这里调用了getService得到INotificationManager服务,源码如下:

  private static INotificationManager sService; 

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

得到INotificationManager服务后,调用了enqueueToast方法将当前的Toast放入到系统的Toast队列中。传的参数分别是pkg、tn和mDuration。也就是说,我们通过Toast.makeText(context, msg, Toast.LENGTH_SHOW).show()去呈现一个Toast,这个Toast并不是立刻显示在当前的window上,而是先进入系统的Toast队列中,然后系统调用回调对象tn的show和hide方法进行Toast的显示和隐藏。
    这里INofiticationManager接口的具体实现类是NotificationManagerService类,位于frameworks/base/services/java/com/android/server/NotificationManagerService.java。
    首先,我们来分析一下Toast入队的函数实现enqueueToast,源码如下:

  public void enqueueToast(String pkg, ITransientNotification callback, int duration)
  {
    // packageName为null或者tn类为null,直接返回,不进队列
    if (pkg == null || callback == null) {
      return ;
    } 

    // (1) 判断是否为系统Toast
    final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg)); 

    // 判断当前toast所属的pkg是否为系统不允许发生Toast的pkg.NotificationManagerService有一个HashSet数据结构,存储了不允许发生Toast的包名
    if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid()) && !areNotificationsEnabledForPackageInt(pkg)) {
      if (!isSystemToast) {
        return;
      }
    } 

    synchronized (mToastQueue) {
      int callingPid = Binder.getCallingPid();
      long callingId = Binder.clearCallingIdentity();
      try {
        ToastRecord record;
        // (2) 查看该Toast是否已经在队列当中
        int index = indexOfToastLocked(pkg, callback);
        // 如果Toast已经在队列中,我们只需要更新显示时间即可
        if (index >= 0) {
          record = mToastQueue.get(index);
          record.update(duration);
        } else {
          // 非系统Toast,每个pkg在当前mToastQueue中Toast有总数限制,不能超过MAX_PACKAGE_NOTIFICATIONS
          if (!isSystemToast) {
            int count = 0;
            final int N = mToastQueue.size();
            for (int i=0; i<N; i++) {
               final ToastRecord r = mToastQueue.get(i);
               if (r.pkg.equals(pkg)) {
                 count++;
                 if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                   Slog.e(TAG, "Package has already posted " + count
                      + " toasts. Not showing more. Package=" + pkg);
                   return;
                 }
               }
            }
          } 

          // 将Toast封装成ToastRecord对象,放入mToastQueue中
          record = new ToastRecord(callingPid, pkg, callback, duration);
          mToastQueue.add(record);
          index = mToastQueue.size() - 1;
          // (3) 将当前Toast所在的进程设置为前台进程
          keepProcessAliveLocked(callingPid);
        }
        // (4) 如果index为0,说明当前入队的Toast在队头,需要调用showNextToastLocked方法直接显示
        if (index == 0) {
          showNextToastLocked();
        }
      } finally {
        Binder.restoreCallingIdentity(callingId);
      }
    }
  }

可以看到,我对上述代码做了简要的注释。代码相对简单,但是还有4点标注代码需要我们来进一步探讨。
    (1) 判断是否为系统Toast。如果当前Toast所属的进程的包名为“android”,则为系统Toast,否则还可以调用isCallerSystem()方法来判断。该方法的实现源码为:

  boolean isUidSystem(int uid) {
    final int appid = UserHandle.getAppId(uid);
    return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
  }
  boolean isCallerSystem() {
    return isUidSystem(Binder.getCallingUid());
  }

isCallerSystem的源码也比较简单,就是判断当前Toast所属进程的uid是否为SYSTEM_UID、0、PHONE_UID中的一个,如果是,则为系统Toast;如果不是,则不为系统Toast。
    是否为系统Toast,通过下面的源码阅读可知,主要有两点优势:

系统Toast一定可以进入到系统Toast队列中,不会被黑名单阻止。
    系统Toast在系统Toast队列中没有数量限制,而普通pkg所发送的Toast在系统Toast队列中有数量限制。

(2) 查看将要入队的Toast是否已经在系统Toast队列中。这是通过比对pkg和callback来实现的,具体源码如下所示:

  private int indexOfToastLocked(String pkg, ITransientNotification callback)
  {
    IBinder cbak = callback.asBinder();
    ArrayList<ToastRecord> list = mToastQueue;
    int len = list.size();
    for (int i=0; i<len; i++) {
      ToastRecord r = list.get(i);
      if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
        return i;
      }
    }
    return -1;
  }

通过上述代码,我们可以得出一个结论,只要Toast的pkg名称和tn对象是一致的,则系统把这些Toast认为是同一个Toast。
    (3) 将当前Toast所在进程设置为前台进程。源码如下所示:

  private void keepProcessAliveLocked(int pid)
  {
    int toastCount = 0; // toasts from this pid
    ArrayList<ToastRecord> list = mToastQueue;
    int N = list.size();
    for (int i=0; i<N; i++) {
      ToastRecord r = list.get(i);
      if (r.pid == pid) {
        toastCount++;
      }
    }
    try {
      mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
    } catch (RemoteException e) {
      // Shouldn't happen.
    }
  }

这里的mAm=ActivityManagerNative.getDefault(),调用了setProcessForeground方法将当前pid的进程置为前台进程,保证不会系统杀死。这也就解释了为什么当我们finish当前Activity时,Toast还可以显示,因为当前进程还在执行。
    (4) index为0时,对队列头的Toast进行显示。源码如下:

  private void showNextToastLocked() {
    // 获取队列头的ToastRecord
    ToastRecord record = mToastQueue.get(0);
    while (record != null) {
      try {
        // 调用Toast的回调对象中的show方法对Toast进行展示
        record.callback.show();
        scheduleTimeoutLocked(record);
        return;
      } catch (RemoteException e) {
        Slog.w(TAG, "Object died trying to show notification " + record.callback
            + " in package " + record.pkg);
        // remove it from the list and let the process die
        int index = mToastQueue.indexOf(record);
        if (index >= 0) {
          mToastQueue.remove(index);
        }
        keepProcessAliveLocked(record.pid);
        if (mToastQueue.size() > 0) {
          record = mToastQueue.get(0);
        } else {
          record = null;
        }
      }
    }
  }

这里Toast的回调对象callback就是tn对象。接下来,我们看一下,为什么系统Toast的显示时间只能是2s或者3.5s,关键在于scheduleTimeoutLocked方法的实现。原理是,调用tn的show方法展示完Toast之后,需要调用scheduleTimeoutLocked方法来将Toast消失。(如果大家有疑问:不是说tn对象的hide方法来将Toast消失,为什么要在这里调用scheduleTimeoutLocked方法将Toast消失呢?是因为tn类的hide方法一执行,Toast立刻就消失了,而平时我们所使用的Toast都会在当前Activity停留几秒。如何实现停留几秒呢?原理就是scheduleTimeoutLocked发送MESSAGE_TIMEOUT消息去调用tn对象的hide方法,但是这个消息会有一个delay延迟,这里也是用了Handler消息机制)。

  private static final int LONG_DELAY = 3500; // 3.5 seconds
  private static final int SHORT_DELAY = 2000; // 2 seconds
  private void scheduleTimeoutLocked(ToastRecord r)
  {
    mHandler.removeCallbacksAndMessages(r);
    Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
    long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
    mHandler.sendMessageDelayed(m, delay);
  }

首先,我们看到这里并不是直接发送了MESSAGE_TIMEOUT消息,而是有个delay的延迟。而delay的时间从代码中“long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;”看出只能为2s或者3.5s,这也就解释了为什么系统Toast的呈现时间只能是2s或者3.5s。自己在Toast.makeText方法中随意传入一个duration是无作用的。
    接下来,我们来看一下WorkerHandler中是如何处理MESSAGE_TIMEOUT消息的。mHandler对象的类型为WorkerHandler,源码如下:

  private final class WorkerHandler extends Handler
  {
    @Override
    public void handleMessage(Message msg)
    {
      switch (msg.what)
      {
        case MESSAGE_TIMEOUT:
          handleTimeout((ToastRecord)msg.obj);
          break;
      }
    }
  }

可以看到,WorkerHandler对MESSAGE_TIMEOUT类型的消息处理是调用了handlerTimeout方法,那我们继续跟踪handleTimeout源码:

  private void handleTimeout(ToastRecord record)
  {
    synchronized (mToastQueue) {
      int index = indexOfToastLocked(record.pkg, record.callback);
      if (index >= 0) {
        cancelToastLocked(index);
      }
    }
  }

handleTimeout代码中,首先判断当前需要消失的Toast所属ToastRecord对象是否在队列中,如果在队列中,则调用cancelToastLocked(index)方法。真相就要浮现在我们眼前了,继续跟踪源码:

  private void cancelToastLocked(int index) {
    ToastRecord record = mToastQueue.get(index);
    try {
      record.callback.hide();
    } catch (RemoteException e) {
      // don't worry about this, we're about to remove it from
      // the list anyway
    }
    mToastQueue.remove(index);
    keepProcessAliveLocked(record.pid);
    if (mToastQueue.size() > 0) {
      // Show the next one. If the callback fails, this will remove
      // it from the list, so don't assume that the list hasn't changed
      // after this point.
      showNextToastLocked();
    }
  }

哈哈,看到这里,我们回调对象的hide方法也被调用了,同时也将该ToastRecord对象从mToastQueue中移除了。到这里,一个Toast的完整显示和消失就讲解结束了。

时间: 2015-07-28

android之自定义Toast使用方法

Android系统默认的Toast十分简洁,使用也非常的简单.但是有时我们的程序使用默认的Toast时会和程序的整体风格不搭配,这个时候我们就需要自定义Toast,使其与我们的程序更加融合. 使用自定义Toast,首先我们需要添加一个布局文件,该布局文件的结构和Activity使用的布局文件结构一致,在该布局文件中我们需设计我们Toast的布局,例如: 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?> &

android自定义toast(widget开发)示例

1.Toast控件: 通过查看源代码,发现Toast里面实现的原理是通过服务Context.LAYOUT_INFLATER_SERVICE获取一个LayoutInflater布局管理器,从而获取一个View对象(TextView),设置内容将其显示 复制代码 代码如下: public static Toast makeText(Context context, CharSequence text, int duration) {        Toast result = new Toast(c

Android开发技巧之永不关闭的Toast信息框(长时间显示而非系统关闭)

Toast信息提示框之所以在显示一定时间后会自动关闭,是因为在系统中有一个Toast队列.系统会依次从队列中取(出队列)一个Toast,并显示它.在显示一段时间后,再关闭,然后再显示下一个Toast信息提示框.直到Toast队列中所有Toast都显示完为止.那么有些时候需要这个Toast信息提示框长时间显示,直到需要关闭它时通过代码来控制,而不是让系统自动来关闭Toast信息提示框.不过这个要求对于Toast本身来说有些过分,因为Toast类并没有提供这个功能.虽然如此,但方法总比问题多.通过一

android开发教程之实现toast工具类

Android中不用再每次都写烦人的Toast了,直接调用这个封装好的类,就可以使用了! 复制代码 代码如下: package com.zhanggeng.contact.tools; /** * Toasttool can make you  use Toast more easy ;  *  * @author ZHANGGeng * @version v1.0.1 * @since JDK5.0 * */import android.content.Context;import andro

如何解决android Toast重复显示

Toast是一种简易的消息提示框,它无法获取焦点,按设置的时间来显示完以后会自动消失,一般用于帮助或提示. 先给大家分享下我的解决思路: 不用计算Toast的时间之类的,就是定义一个全局的成员变量Toast, 这个Toast不为null的时候才去make,否则直接setText.为了按返回键后立即使Toast不再显示,重写父类Activity的onBackPressed()方法里面去cancel你的Toast即可. 代码: private Toast mToast; public void sh

Android编程经典代码集锦(复制,粘贴,浏览器调用,Toast显示,自定义Dialog等)

本文实例总结了Android编程经典代码段.分享给大家供大家参考,具体如下: 1. 复制,粘贴 clip = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); clip.setText("copy"); // 复制 clip.getText(); // 粘贴 2.调用浏览器 核心代码如下: Intent intent = new Intent(); intent.setAction("android.

Android 5.0以上Toast不显示的解决方法

原因分析 用户使用android 5.0以上的系统在安装APP时,将消息通知的权限关闭掉了.实际上用户本意只是想关闭Notification,但是Toast的show方法中有调用INotificationManager这个类,而这个类在用户关闭消息通知权限的同时被禁用了,所以我们的吐司无法显示. Toast.show() 效果图 自定义Toast(上)与Toast(下)比对 问题解决 既然系统不允许我们调用Toast,那么我们就自立门户--自己写一个Toast出来.我们总体的思路是:在Activ

Android中使用Toast.cancel()方法优化toast内容显示的解决方法

产品在测试过程中发现一个bug,就是测试人员不停的疯狂的点击某个按钮,触发了toast以后,toast内容会一直排着队的显示出来,不能很快的消失.这样可能会影响用户的使用. 看到Toast有一个cancel()方法: 复制代码 代码如下: void cancel() Close the view if it's showing, or don't show it if it isn't showing yet. 做程序员的,基本一看api就知道,用这个可以取消上一个toast的显示,然后显示下一

Android AndBase框架内部封装实现进度框、Toast框、弹出框、确认框(二)

本文是针对AndBase框架学习整理的第二篇笔记,想要了解AndBase框架的朋友可以阅读本文,大家共同学习. 使用AbActivity内部封装的方法实现进度框,Toast框,弹出框,确认框 AndBase中AbActivity封装好了许多方法提供我们去使用,使得在使用的时候更加的方便,只需要传递相关参数即可..省去了我们自己使用基础的函数进行构造... 就好比进度框,Toast框,弹出框,确认框...这些基本的东西都在AndBase的AbActivity封装好了...我们只需要传递参数调用其中

Android控件系列之Toast使用介绍

Toast英文含义是吐司,在Android中,它就像烘烤机里做好的吐司弹出来,并持续一小段时间后慢慢消失 Toast也是一个容器,可以包含各种View,并承载着它们显示. 使用场景: 1.需要提示用户,但又不需要用户点击"确定"或者"取消"按钮. 2.不影响现有Activity运行的简单提示. 用法: 1.可以通过构造函数初始化: 复制代码 代码如下: //初始化Toast Toast toast = new Toast(this); //设置显示时间,可以选择To

Android控件系列之CheckBox使用介绍

学习目的: 1.掌握在Android中如何建立CheckBox 2.掌握CheckBox的常用属性 3.掌握CheckBox选中状态变换的事件(监听器) CheckBox简介: CheckBox和Button一样,也是一种古老的控件,它的优点在于,不用用户去填写具体的信息,只需轻轻点击,缺点在于只有"是"和"否"两种情况,但我们往往利用它的这个特性,来获取用户的一些信息. 如一个身份表单中,常常让用户填写"是否已经结婚",显然让用户去填写&quo

Android控件系列之TextView使用介绍

学习目的: 1.了解在Android中如何使用TextView控件 2.掌握TextView控件重要属性 作用:TextView类似一般UI中的Label,TextBlock等控件,只是为了单纯的显示一行或多行文本 上图的XML布局如下: 复制代码 代码如下: <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_c

Android控件系列之Button以及Android监听器使用介绍

学习目的: 1.掌握在Android中如何建立Button 2.掌握Button的常用属性 3.掌握Button按钮的点击事件(监听器) Button是各种UI中最常用的控件之一,它同样也是Android开发中最受欢迎的控件之一,用户可以通过触摸它来触发一系列事件,要知道一个没有点击事件的Button是没有任何意义的,因为使用者的固定思维是见到它就想去点! 先看下Android中普通Button的样子: 以及点中Button后的样子: 我在Android控件系列之XML静态资源中已经强调了布局和

Android控件系列之RadioButton与RadioGroup使用方法

学习目的: 1.掌握在Android中如何建立RadioGroup和RadioButton 2.掌握RadioGroup的常用属性 3.理解RadioButton和CheckBox的区别 4.掌握RadioGroup选中状态变换的事件(监听器) RadioButton和CheckBox的区别: 1.单个RadioButton在选中后,通过点击无法变为未选中 单个CheckBox在选中后,通过点击可以变为未选中 2.一组RadioButton,只能同时选中一个 一组CheckBox,能同时选中多个

Android控件系列之XML静态资源使用介绍

学习目的: 1.了解在Android中如何设置和调用XML资源 2.掌握如何利用XML和JAVA代码进行协同开发界面 3.理解R文件的作用 开发Android时,总能看到一个系统自动生成的R.java文件: 您必须了解一下几个要点: 1.R.java是自动生成的,并且强烈建议您不要去手动修改其中的代码.2.R类中的若干个内部类的名字和Android项目中的res文件下的子文件名字一致(除了drawable自动分为了高中低3个等级): 您的项目中可能不是如上图中的文件或代码,但它们一定符合上述的规

Android控件系列之EditText使用方法

学习目的: 1.掌握在Android中如何建立EditText2.掌握EditText的常用属性3.掌握EditText焦点的事件.按键的事件(监听器) 介绍: EditText是接受用户输入信息的最重要控件.通过前面课程的学习,您可能会猜到可以利用EditText.getText()获取它的文本,但真正的项目中,可能没那么简单,需要更多的限制,如文本长度限制,是否数字限制等等. 鉴于手机屏幕尺寸有限,您可能总想着如何节约控件.在每个用户需要填写内容的文本框的左边加上标题在PC上是一种优雅的方法

Android控件系列之相册Gallery&amp;Adapter适配器入门&amp;控件缩放动画入门

学习目的: 1.掌握在Android中如何建立Gallery 2.初步理解Android适配器的原理 3.实现简单的控件缩放动画 简介: 1.Gallery是Android内置的一个控件,它可以继承若干图片甚至是其他控件 2.Gallery自带了滚动播放图片功能,此功能您可以通过模拟器拖曳鼠标或者在手机上拖拽验证 3.Gallery需要适配器来传输数据,如果您不熟悉"适配器设计模式",可以将适配器理解为某厂商的电脑适配器,只要这个厂商的所有型号的电脑都能使用该适配器,也就是说,设计新型

Android控件系列之Shape使用方法

如果你对Android系统自带的UI控件感觉不够满意,可以尝试下自定义控件,我们就以Button为例,很早以前Android123就写到过Android Button按钮控件美化方法里面提到了xml的selector构造.当然除了使用drawable这样的图片外今天Android开发网谈下自定义图形shape的方法,对于Button控件Android上支持以下几种属性shape.gradient.stroke.corners等. 复制代码 代码如下: 我们就以目前系统的Button的select

Android控件系列之ImageView使用方法

学习目的: 1.掌握在Android中如何插入图片 图片的加入可以立刻让您的程序增色不少,我们样例选用一张Android机器人(picture.jpg),您可以使用自己的任何图片进行试验 一般建议您程序中的图片加入资源,而不是放在SD卡中用流的方式去读取,毕竟嵌入的资源比较安全,不容易被篡改. 1.导入图片到资源 将图片拖拽到项目res\drawable开头的3个文件夹下,他们分别代表了高.中.低分辨度的图片.Android读取图片时自动优化,选用合适的一个图片显示,比如高分辨率可以存放128*