基于Android浮动组件 可用于应用中新功能展示等


[日期:2011-12-11]

作者:michael__li


基于Android的浮动组件,可以用于应用中的新功能展示等等。

前言



在开发Android应用时,加新功能是必不可少的,我们加入了新的功能,有的一看界面就可以看出来,但是有的新功能就比较隐蔽,也就是用户很难知道你添加了这个新功能,这个时候就需要用户在打开我们的应用时给出一些提示,说明我们在哪里添加了新功能,点击哪里可以看到这个新功能。这时我们第一时间想到的可能是Toast,因为它用法简单,又不影响用户操作,但是它有个缺点,就是不能明确的指示是哪里添加了新功能,除非你用文字描述出来。为此,我基于Toast编写了一个小组件FloatTextToast(下面遇到的这个名字代替我写的这个组件),他和Toast的用法一样简单,并且弥补了Toast的缺点,也更显得更好看。




组件源代码和效果图的Demo下载



免费下载地址在 http://linux.linuxidc.com/

用户名与密码都是www.linuxidc.com

具体下载目录在 /pub/Android源码集锦/2011年/12月/基于Android浮动组件 可用于应用中新功能展示等/

效果图





你可以学到



  1. Toast的基本用法
  2. Android的消息机制,如何创建自己的消息队列
  3. 怎样在Activity启动时获取一个View的width、height、top、left等属性

基本思路



  1. 首先你要有一个处理好的9 PNG的图片,用于自适应文字显示,关于9 PNG处理可以参考Android Doc
  2. 要显示在哪个View的下面,就要知道这个目标View的位置
  3. 把要显示的文本放在一个TextView里,使用Toast的setView方法设置Toast要显示的View。
  4. 根据得到的位置,最后就是使用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构造函数,可以对它有个大概的了解,防止后面的代码中出现的成员变量不认识。

    1. private
    2. this.mTargetView = targetView;   
    3. this.mContext= context;   
    4. new
    5. new
    6.         mContentView.setBackgroundResource(R.drawable.float_text_toast_bg);   
    7.         mContentView.setTextColor(Color.BLACK);   
    8. 16);   
    9.         mToast.setView(mContentView);   
    10.            
    11. //初始化一个Handler线程 
    12. new HandlerThread("FloatTextToast");   
    13.         mHandlerThread.start();   
    14. new
    15.     }



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



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


    1. //初始化一个Handler线程 
    2. new HandlerThread("FloatTextToast");   
    3.         mHandlerThread.start();   
    4. new

      1. private class FloatTextToastHandler extends
      2.    
      3. public
      4. super(looper);   
      5.         }   
      6.    
      7. @Override
      8. public void
      9. switch(msg.what){   
      10. case
      11.                 showInHandler();   
      12.             }   
      13.         }   
      14.            
      15.            
      16.     }



      1. mHandler=new



        1. /**在Handler调用的show方法,主要为了等待{@link #mTargetView}的位置*/
        2. private void
        3. int[] targetPos=getTargetViewPos();   
        4. if(targetPos[0]==0&&targetPos[1]==0){   
        5. 100);   
        6. else{   
        7. final
        8.             mToast.setGravity(Gravity.LEFT|Gravity.TOP, contentPos.left, contentPos.top);   
        9.             mToast.show();   
        10.         }   
        11.     }

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




        获取要显示文本的位置



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

        1. private int[] getTargetViewPos(){  
        2. final int[] targetPos=new int[2];  
        3.         mTargetView.getLocationInWindow(targetPos);  
        4. return
        5.     }

        1. /**
        2.      * 计算获取浮动文本显示的位置,把浮动文本放在targetView的中心处
        3.      * @return 一个包含top和left的Rect
        4.      */
        5. private  Rect getContentViewPos(int[] targetPos){  
        6. final Rect windowVisibleRect=new
        7. final
        8. final
        9. //状态栏高度 
        10.         targetView.getWindowVisibleDisplayFrame(windowVisibleRect);  
        11. int
        12.           
        13. //背景图那个三角箭头的位置 
        14. final
        15. int contentW=(int)textPaint.measureText((String)contentView.getText());  
        16. int arrowPos=(int)(contentW*(30.0/160));  
        17.           
        18. final Rect rect = new
        19. 0]+targetView.getWidth()/2-arrowPos;  
        20. 1]-statusBarHeight + targetView.getHeight();  
        21. return
        22.     }

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





        完整的组件代码

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

        1. /**
        2.  * 浮动的文本显示。根据一个提供的View,可以把文本显示到该View的下面.
        3.  * 可以设置显示的时间,多了该时间后自动消失。目前只支持纯文本{@link String}类型的显示
        4.  * 因为要计算显示文本的宽度。
        5.  * @author michael_li(飞雪无情)
        6.  * @since 2011-12-10 下午04:57:36
        7.  */
        8. public class
        9. public static final int
        10. public static final int
        11. private static final int WHAT_SHOW=1;  
        12.       
        13. private
        14. private
        15. private
        16. private
        17.       
        18. private
        19. private
        20. private
        21. this.mTargetView = targetView;  
        22. this.mContext= context;  
        23. new
        24. new
        25.         mContentView.setBackgroundResource(R.drawable.float_text_toast_bg);  
        26.         mContentView.setTextColor(Color.BLACK);  
        27. 16);  
        28.         mToast.setView(mContentView);  
        29.           
        30. //初始化一个Handler线程 
        31. new HandlerThread("FloatTextToast");  
        32.         mHandlerThread.start();  
        33. new
        34.     }  
        35. /**
        36.      * 生成一个FloatTextToast
        37.      * @param context Activity 上下文
        38.      * @param targetView  目标View,浮动文本要显示在哪个View下面
        39.      * @param text 要显示的文本
        40.      * @param duration 浮动文本显示的时间 {@link #LENGTH_LONG} {@link #LENGTH_SHORT}
        41.      * @return 一个FloatTextToast,可以调用{@link #show()}显示
        42.      */
        43. public static FloatTextToast makeText(Context context,View targetView, String text, int
        44. final FloatTextToast floatToast=new
        45. final
        46.         contentView.setText(text);  
        47.         floatToast.mToast.setDuration(duration);  
        48. return
        49.     }  
        50. /**
        51.      * 显示浮动文本
        52.      */
        53. public void
        54.         mHandler.sendEmptyMessage(WHAT_SHOW);  
        55.     }  
        56. /**在Handler调用的show方法,主要为了等待{@link #mTargetView}的位置*/
        57. private void
        58. int[] targetPos=getTargetViewPos();  
        59. if(targetPos[0]==0&&targetPos[1]==0){  
        60. 100);  
        61. else{  
        62. final
        63.             mToast.setGravity(Gravity.LEFT|Gravity.TOP, contentPos.left, contentPos.top);  
        64.             mToast.show();  
        65.         }  
        66.     }  
        67. private int[] getTargetViewPos(){  
        68. final int[] targetPos=new int[2];  
        69.         mTargetView.getLocationInWindow(targetPos);  
        70. return
        71.     }  
        72. /**
        73.      * 计算获取浮动文本显示的位置,把浮动文本放在targetView的中心处
        74.      * @return 一个包含top和left的Rect
        75.      */
        76. private  Rect getContentViewPos(int[] targetPos){  
        77. final Rect windowVisibleRect=new
        78. final
        79. final
        80. //状态栏高度 
        81.         targetView.getWindowVisibleDisplayFrame(windowVisibleRect);  
        82. int
        83.           
        84. //背景图那个三角箭头的位置 
        85. final
        86. int contentW=(int)textPaint.measureText((String)contentView.getText());  
        87. int arrowPos=(int)(contentW*(30.0/160));  
        88.           
        89. final Rect rect = new
        90. 0]+targetView.getWidth()/2-arrowPos;  
        91. 1]-statusBarHeight + targetView.getHeight();  
        92. return
        93.     }  
        94. private class FloatTextToastHandler extends
        95.   
        96. public
        97. super(looper);  
        98.         }  
        99.   
        100. @Override
        101. public void
        102. switch(msg.what){  
        103. case
        104.                 showInHandler();  
        105.             }  
        106.         }  
        107.           
        108.           
        109.     }  
        110. }



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

          1. FloatTextToast.makeText(Context context, View targetView, String text, int



          小结


          这里主要是通过类之间的组合编写一个一个FloatTextToast组件,便于在应用中提示一些信息,不光局限于新功能的提示,还有其他的点击查看个人信息等等,就如上面的效果图一样。这里主要的难点就在于Activity启动获取targetView的状态,这里采用了不受影响的自定义的消息机制,能及时的获取targetView的状态。这里也采用的Toast的队列机制,这样就能够更好的一个个的提示,让用户看完一个再显示另外一个,不至于一下子全显示出来,而用户没有时间看。这里还采用了Paint用于测量文本的真实宽度,所以也有了一些缺陷,如果哪位有更好的方法,也可以留言告知我,不胜感激。