• 浮动组建(转)


    在开发Android应用时,加新功能是必不可少的,我们加入了新的功能,有的一看界面就可以看出来,但是有的新功能就比较隐蔽,也就是用户很难知道你添加了这个新功能,这个时候就需要用户在打开我们的应用时给出一些提示,说明我们在哪里添加了新功能,点击哪里可以看到这个新功能。这时我们第一时间想到的可能是Toast,因为它用法简单,又不影响用户操作,但是它有个缺点,就是不能明确的指示是哪里添加了新功能,除非你用文字描述出来。

    基本思路                                                                                      

    • 首先你要有一个处理好的9 PNG的图片,用于自适应文字显示,关于9 PNG处理可以参考Android Doc
    • 要显示在哪个View的下面,就要知道这个目标View的位置
    • 把要显示的文本放在一个TextView里,使用Toast的setView方法设置Toast要显示的View。
    • 根据得到的位置,最后就是使用Toast的setGravity方法把要显示的内容放到正确的位置显示出来即可。
    • 总的来说首先就是要知道目标View,根据targetView计算出要显示提示的位置,然后根据位置使用Toast把提示的文本显示出来。但是这里有几个难点,下面就一一解决

      Activity加载完成时获取targetVIew的宽高和位置属性                     

      我们加入了新的功能提示,自然会在用户打开这个界面的时候就提示,但是在UI没有渲染完成绑定倒Window上的时候,是不能获取倒targetView的width、height和position的,那么我们怎么才能知道targetView的这些属性呢?Activity的onAttachedToWindow回调方法是不能用的,况且它是在API 5加上的,以前的API中并没有。不过我们还有一种方法,那就是在显示提示的时候获取targetView的属性,如果获取不到(为0)就一直获取,直到获取到为止,这其实是一个轮询。为了达到这一目的,我们在开发者调用FloatTextToast.show()的时候使用Android的Message机制轮询获取一个targetView的属性,如果获取到,就会显示提示文字了。在此之前先看下FloatTextToast构造函数,可以对它有个大概的了解,防止后面的代码中出现的成员变量不认识。

      复制代码
      /**
           * 私有的构造函数
           * 
           * @param context
           *            上下文
           * @param targetView
           *            目标view
           * @param content
           *            内容
           * @param time
           *            显示时间
           */
          private MyToast(Context context, View targetView, String content, int time) {
              this.targetView = targetView;
              this.context = context;
              this.content = content;
              this.time = time;
              // 初始化Toast
              toast = new Toast(this.context);
              textView = new TextView(this.context);
              textView.setTextColor(Color.BLACK);
              textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
              textView.setBackgroundResource(R.drawable.float_text_toast_bg);
              textView.setText(this.content);
              toast.setView(textView);// 设置背景
              
              toast.setDuration(time);// 设置时间
              // 开始handlerthread
              handlerThread = new HandlerThread("MyToast");
              handlerThread.start();
              // 构造一个自己的looper
              handler = new MyHandler(handlerThread.getLooper());
          }
      复制代码

      自定义自己的消息循环机制                                                              

      要想在一个自定义的组件中使用Message机制,一定要有自己的Looper机制,我们不能使用Activity的Looper,因为主Looper可能会有其他的Message需要处理,这就会导致我们的show方法会延迟调用,这样效果就不好了,所以要有一个专门的Looper来处理此Message。要声明自己的Looper,就需要HandlerThread这个类的配合了,这可是个好东西,使用它你会很容易的创建一个自己的线程用于处理你Message。使用方法很简单,如下代码:

      // 开始handlerthread
              handlerThread = new HandlerThread("MyToast");
              handlerThread.start();
              // 构造一个自己的looper
              handler = new MyHandler(handlerThread.getLooper());

      这样就声明了一个HandlerThread并且让它运行,运行之后我们就可以获取一个属于该Thread的Looper,然后把Message发送给这个Looper,那么这个线程就可以处理你发送的消息了。。看看我们的自定义Handler

      复制代码
      /**
           * 自定义的Handler
           * 
           * @author sansung
           * 
           */
          class MyHandler extends Handler {
      
              public MyHandler(Looper looper) {
                  super(looper);
                  // TODO 自动生成的构造函数存根
              }
      
              @Override
              public void handleMessage(Message msg) {
                  // TODO 自动生成的方法存根
                  super.handleMessage(msg);
                  switch (msg.what) {
                  case WHAT_SHOW:
                      showInHandler();
                      break;
                  default:
                      break;
                  }
              }
      
          }
      复制代码

      它需要传递一个Looper作为构造参数声明,意思就是使用这个Looper处理我发send的Message的意思。上面的代码

      handler = new MyHandler(handlerThread.getLooper());

      正是我们使用自己开启的线程处理我们的Message的意思。下面看下我们的showInHandler()方法是怎么处理的。

      复制代码
      /**
           * Handler调用的show方法,主要为了等待targetView的位置
           * 如果targetView的位置没有得到,handler looper继续循环获取
           */
          private void showInHandler() {
              int[] targetPos = getTargetViewPos();
              if(targetPos[0]==0&&targetPos[1]==0){
                  handler.sendEmptyMessageDelayed(WHAT_SHOW, 100);
              }else{
                  final Rect contentPos=getSize(targetPos);
                  toast.setGravity(Gravity.LEFT|Gravity.TOP, contentPos.left, contentPos.top);
                  toast.show();
              }
          }
      复制代码

      该方法其实就是在获取targetVIew的位置,如果获取不到,则向自定义的Looper里发送一个Message重新调用该函数,如果得到了位置,那么就调用Toast的setGravity方法设置好要显示文本的位置,然后显示即可。

      获取要显示文本的位置                                                                    

      要获取显示的位置,就要知道targetVIew的位置以及它的宽、高,这样就能计算要显示文本的位置了。View组件都有一个函数,可以把自己在Window里的坐标转换为一个数组。

      复制代码
      /**
           * 得到目标View的位置
           * @return
           */
          private int[] getTargetViewPos() {
              final int[] targetPos = new int[2];
              targetView.getLocationInWindow(targetPos);
              return targetPos;
          }
      复制代码

      这样,就返回了targetView的xy坐标了。光有targetView的坐标还不够,还要有contentView最终要显示的位置。

      复制代码
      /**
           * 计算获取浮动文本显示的位置,把浮动文本放在targetView的中心处
           * 
           * @param targetPos
           * @return 一个包含top和left的Rect
           */
          private Rect getSize(int[] targetPos) {
              final Rect windowVisibleRect = new Rect();
              final View targetView = this.targetView;
              final TextView contentView = textView;
              // 状态栏高度
              targetView.getWindowVisibleDisplayFrame(windowVisibleRect);
              int statusBarHeight = windowVisibleRect.top;
              // 背景图那个三角箭头的位置
              final TextPaint textPaint = contentView.getPaint();
              int contentW = (int) textPaint.measureText((String) contentView
                      .getText());
              int arrowPos = (int) (contentW * (30.0 / 160));
      
              final Rect rect = new Rect();
              rect.left = targetPos[0] + targetView.getWidth() / 2 - arrowPos;
              rect.top = targetPos[1] - statusBarHeight + targetView.getHeight();
              return rect;
          }
      复制代码

      这个函数的功能就是让文本显示在targetView的下方的横向中间的位置,也就是文本的背景尖角三角要指向targetView横向中间的位置,这样才好看些。为了这个才需要使用Paint测量文本的宽度,所以这也是该组件的一个缺陷,不能显示String格式之外的字符,比如SpannableString。

      完整的组件代码                                                                            

      上面是对组件代码的拆分讲解,是为了说明我们当时实现这个组件的想法以及步骤,下面就整体把代码列出来,明了的看一下。

      复制代码
      public class MyToast {
          private static final int WHAT_SHOW = 1;
          private View targetView = null;// 目标view
          private Context context = null;// Toast的上下文
          private Toast toast = null;// 显示的土司
          private String content = null;// 土司显示内容
          private TextView textView = null;// 土司中的textview
          private int time = 0;// 土司显示时间
          private HandlerThread handlerThread = null;// handlerThread
          private static MyToast myToast = null;
          private Handler handler = null;
      
          /**
           * 私有的构造函数
           * 
           * @param context
           *            上下文
           * @param targetView
           *            目标view
           * @param content
           *            内容
           * @param time
           *            显示时间
           */
          private MyToast(Context context, View targetView, String content, int time) {
              this.targetView = targetView;
              this.context = context;
              this.content = content;
              this.time = time;
              // 初始化Toast
              toast = new Toast(this.context);
              textView = new TextView(this.context);
              textView.setTextColor(Color.BLACK);
              textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
              textView.setBackgroundResource(R.drawable.float_text_toast_bg);
              textView.setText(this.content);
              toast.setView(textView);// 设置背景
              
              toast.setDuration(time);// 设置时间
              // 开始handlerthread
              handlerThread = new HandlerThread("MyToast");
              handlerThread.start();
              // 构造一个自己的looper
              handler = new MyHandler(handlerThread.getLooper());
          }
      
          /**
           * 显示Toast
           * 
           * @param context
           *            上下文
           * @param targetView
           *            目标view
           * @param content
           *            内容
           * @param time
           *            显示时间
           */
          public static MyToast makeText(Context context, View targetView,
                  String content, int time) {
              myToast = new MyToast(context, targetView, content, time);
              return myToast;
      
          }
      
          public void show() {
              handler.sendEmptyMessage(WHAT_SHOW);
          }
      
          /**
           * 计算获取浮动文本显示的位置,把浮动文本放在targetView的中心处
           * 
           * @param targetPos
           * @return 一个包含top和left的Rect
           */
          private Rect getSize(int[] targetPos) {
              final Rect windowVisibleRect = new Rect();
              final View targetView = this.targetView;
              final TextView contentView = textView;
              // 状态栏高度
              targetView.getWindowVisibleDisplayFrame(windowVisibleRect);
              int statusBarHeight = windowVisibleRect.top;
              // 背景图那个三角箭头的位置
              final TextPaint textPaint = contentView.getPaint();
              int contentW = (int) textPaint.measureText((String) contentView
                      .getText());
              int arrowPos = (int) (contentW * (30.0 / 160));
      
              final Rect rect = new Rect();
              rect.left = targetPos[0] + targetView.getWidth() / 2 - arrowPos;
              rect.top = targetPos[1] - statusBarHeight + targetView.getHeight();
              return rect;
          }
          /**
           * Handler调用的show方法,主要为了等待targetView的位置
           * 如果targetView的位置没有得到,handler looper继续循环获取
           */
          private void showInHandler() {
              int[] targetPos = getTargetViewPos();
              if(targetPos[0]==0&&targetPos[1]==0){
                  handler.sendEmptyMessageDelayed(WHAT_SHOW, 100);
              }else{
                  final Rect contentPos=getSize(targetPos);
                  toast.setGravity(Gravity.LEFT|Gravity.TOP, contentPos.left, contentPos.top);
                  toast.show();
              }
          }
          /**
           * 得到目标View的位置
           * @return
           */
          private int[] getTargetViewPos() {
              final int[] targetPos = new int[2];
              targetView.getLocationInWindow(targetPos);
              return targetPos;
          }
      
          /**
           * 自定义的Handler
           * 
           * @author sansung
           * 
           */
          class MyHandler extends Handler {
      
              public MyHandler(Looper looper) {
                  super(looper);
                  // TODO 自动生成的构造函数存根
              }
      
              @Override
              public void handleMessage(Message msg) {
                  // TODO 自动生成的方法存根
                  super.handleMessage(msg);
                  switch (msg.what) {
                  case WHAT_SHOW:
                      showInHandler();
                      break;
                  default:
                      break;
                  }
              }
      
          }
      
      }
      复制代码

      此组件和Toast的实现方法一样,所以上手不难,只需使用makeText静态方法生成一个即可

      MyToast.makeText(MainActivity.this, v, "12222222222222221111111111", 1).show();

      我是天王盖地虎的分割线                                                                 

      1

      源代码:http://pan.baidu.com/s/1dD1Qx01

      自定义Toast.zip

      参考:http://flysnow.iteye.com/blog/1760962

  • 相关阅读:
    数据结构-线性表-链表
    [poj 2991]Crane[线段树表示向量之和,而非数量]
    vector要注意的点
    vector与deque的区别
    sendfile复习
    GPU和CPU的区别
    常用Linux命令 mount df dd
    可省略的关键字
    父类与子类的virtual
    加快编译的技巧 & mount及tmpfs
  • 原文地址:https://www.cnblogs.com/dongweiq/p/3927179.html
Copyright © 2020-2023  润新知