zoukankan      html  css  js  c++  java
  • 浮动组建(转)

    在开发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

  • 相关阅读:
    STL源码剖析之_allocate函数
    PAT 1018. Public Bike Management
    PAT 1016. Phone Bills
    PAT 1012. The Best Rank
    PAT 1014. Waiting in Line
    PAT 1026. Table Tennis
    PAT 1017. Queueing at Bank
    STL源码剖析之list的sort函数实现
    吃到鸡蛋好吃,看看是哪只母鸡下的蛋:好用的Sqlite3
    cJSON
  • 原文地址:https://www.cnblogs.com/dongweiq/p/3927179.html
Copyright © 2011-2022 走看看