相信大家都体验过android通讯录中的弹窗效果。如图所示:

android中提供了QuickContactBadge来实现这一效果。这里简单演示下。

首先创建布局文件:

        
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <QuickContactBadge
  8. android:id="@+id/badge"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:src="@drawable/icon">
  12. </QuickContactBadge>
  13. </LinearLayout>

很简单,在布局中添加一个QuickContactBadge组件即可。

在Activity中配置:

        
  1. publicclassQuickcontactActivityextendsActivity{
  2. /**Calledwhentheactivityisfirstcreated.*/
  3. @Override
  4. publicvoidonCreate(BundlesavedInstanceState){
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.main);
  7. QuickContactBadgesmallBadge=(QuickContactBadge)findViewById(R.id.badge);
  8. //从email关联一个contact
  9. smallBadge.assignContactFromEmail("notice520@gmail.com",true);
  10. //设置窗口模式
  11. smallBadge.setMode(ContactsContract.QuickContact.MODE_SMALL);
  12. }
  13. }

注意加入读通讯录的权限

<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>  

实现效果如图:

但是这个组件局限性很大,弹出窗口中只能是一些contact操作。但是仔细一想,这样的操作并不难,不就是一个带动画的弹窗么。下面就来我们自己实现一个。

实现一个带动画的弹窗并不难,在我的之前一篇博客中有讲过弹窗PopupWindow的使用,不清楚弹窗的朋友可以去看下。在这里实现的难点主要有这些:

1.判断基准view在屏幕中的位置,从而确定弹窗弹出的位置以及动画。这是非常重要的一点,或许基准在屏幕上方,那么就要向下弹出。

2.动态的添加弹窗中的按钮,并实现点击

3.箭头位置的控制。箭头应该保持在基准的下方。

4.动画的匹配。里面有两种动画。一种是PopupWindow弹出动画,我们通过设置弹窗的style来实现(style的用法可以参考我之前的博客)。另一种是弹窗中间的布局的动画。

了解了难点以后,写起来就方便了。

首先实现弹窗的布局:

        
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="wrap_content"
  4. android:layout_height="wrap_content">
  5. <FrameLayout
  6. android:layout_marginTop="10dip"
  7. android:id="@+id/header2"
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:background="@drawable/quickcontact_top_frame"/>
  11. <ImageView
  12. android:id="@+id/arrow_up"
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:src="@drawable/quickcontact_arrow_up"/>
  16. <HorizontalScrollView
  17. android:id="@+id/scroll"
  18. android:layout_width="fill_parent"
  19. android:layout_height="wrap_content"
  20. android:fadingEdgeLength="0dip"
  21. android:layout_below="@id/header2"
  22. android:background="@drawable/quickcontact_slider_background"
  23. android:scrollbars="none">
  24. <LinearLayout
  25. android:id="@+id/tracks"
  26. android:layout_width="wrap_content"
  27. android:layout_height="wrap_content"
  28. android:paddingTop="4dip"
  29. android:paddingBottom="4dip"
  30. android:orientation="horizontal">
  31. <ImageView
  32. android:layout_width="wrap_content"
  33. android:layout_height="wrap_content"
  34. android:src="@drawable/quickcontact_slider_grip_left"/>
  35. <ImageView
  36. android:layout_width="wrap_content"
  37. android:layout_height="wrap_content"
  38. android:src="@drawable/quickcontact_slider_grip_right"/>
  39. </LinearLayout>
  40. </HorizontalScrollView>
  41. <FrameLayout
  42. android:id="@+id/footer"
  43. android:layout_width="fill_parent"
  44. android:layout_height="wrap_content"
  45. android:layout_below="@id/scroll"
  46. android:background="@drawable/quickcontact_bottom_frame"/>
  47. <ImageView
  48. android:id="@+id/arrow_down"
  49. android:layout_width="wrap_content"
  50. android:layout_height="wrap_content"
  51. android:layout_marginTop="-1dip"
  52. android:layout_below="@id/footer"
  53. android:src="@drawable/quickcontact_arrow_down"/>
  54. </RelativeLayout>

窗体内部使用一个HorizontalScrollView可以实现一个滑动效果。我们可以动态的在这个布局中添加按钮,我们称作Actionitem。

写一个ActionItem类,使得我们可以用一个ArrayList做容器,动态的添加这些actionitem。这些都是服务于第二个难点。

        
  1. packagecom.notice.quickaction;
  2. importandroid.graphics.drawable.Drawable;
  3. importandroid.view.View.OnClickListener;
  4. /**
  5. *Actionitem,每个item里面都有一个ImageView和一个TextView
  6. */
  7. publicclassActionItem{
  8. privateDrawableicon;
  9. privateStringtitle;
  10. privateOnClickListenerlistener;
  11. /**
  12. *构造器
  13. */
  14. publicActionItem(){
  15. }
  16. /**
  17. *带Drawable参数的构造器
  18. */
  19. publicActionItem(Drawableicon){
  20. this.icon=icon;
  21. }
  22. /**
  23. *设置标题
  24. */
  25. publicvoidsetTitle(Stringtitle){
  26. this.title=title;
  27. }
  28. /**
  29. *获得标题
  30. *
  31. *@returnactiontitle
  32. */
  33. publicStringgetTitle(){
  34. returnthis.title;
  35. }
  36. /**
  37. *设置图标
  38. */
  39. publicvoidsetIcon(Drawableicon){
  40. this.icon=icon;
  41. }
  42. /**
  43. *获得图标
  44. */
  45. publicDrawablegetIcon(){
  46. returnthis.icon;
  47. }
  48. /**
  49. *绑定监听器
  50. */
  51. publicvoidsetOnClickListener(OnClickListenerlistener){
  52. this.listener=listener;
  53. }
  54. /**
  55. *获得监听器
  56. */
  57. publicOnClickListenergetListener(){
  58. returnthis.listener;
  59. }
  60. }

接下来就是这个弹窗的实现了,我们继承PopupWindow类。在这个类中我们需要实现通过位置设置动画及弹出位置,并且给出一个方法供实现类调用,来动态添加item和设置动画效果。

代码如下:

        
  1. packagecom.notice.quickaction;
  2. importjava.util.ArrayList;
  3. importandroid.content.Context;
  4. importandroid.graphics.Rect;
  5. importandroid.graphics.drawable.BitmapDrawable;
  6. importandroid.graphics.drawable.Drawable;
  7. importandroid.view.Gravity;
  8. importandroid.view.LayoutInflater;
  9. importandroid.view.MotionEvent;
  10. importandroid.view.View;
  11. importandroid.view.View.OnClickListener;
  12. importandroid.view.View.OnTouchListener;
  13. importandroid.view.ViewGroup;
  14. importandroid.view.ViewGroup.LayoutParams;
  15. importandroid.view.WindowManager;
  16. importandroid.view.animation.Animation;
  17. importandroid.view.animation.AnimationUtils;
  18. importandroid.view.animation.Interpolator;
  19. importandroid.widget.ImageView;
  20. importandroid.widget.LinearLayout;
  21. importandroid.widget.PopupWindow;
  22. importandroid.widget.TextView;
  23. /**
  24. *继承弹窗,构造我们需要的弹窗
  25. */
  26. publicclassQuickActionsextendsPopupWindow{
  27. privatefinalViewroot;
  28. privatefinalImageViewmArrowUp;
  29. privatefinalImageViewmArrowDown;
  30. privatefinalAnimationmTrackAnim;
  31. privatefinalLayoutInflaterinflater;
  32. privatefinalContextcontext;
  33. protectedfinalViewanchor;
  34. protectedfinalPopupWindowwindow;
  35. privateDrawablebackground=null;
  36. protectedfinalWindowManagerwindowManager;
  37. protectedstaticfinalintANIM_GROW_FROM_LEFT=1;
  38. protectedstaticfinalintANIM_GROW_FROM_RIGHT=2;
  39. protectedstaticfinalintANIM_GROW_FROM_CENTER=3;
  40. protectedstaticfinalintANIM_AUTO=4;
  41. privateintanimStyle;
  42. privatebooleananimateTrack;
  43. privateViewGroupmTrack;
  44. privateArrayList<ActionItem>actionList;
  45. /**
  46. *构造器,在这里初始化一些内容
  47. *
  48. *@paramanchor像我之前博客所说的理解成一个基准弹窗以此为基准弹出
  49. */
  50. publicQuickActions(Viewanchor){
  51. super(anchor);
  52. this.anchor=anchor;
  53. this.window=newPopupWindow(anchor.getContext());
  54. //在popwindow外点击即关闭该window
  55. window.setTouchInterceptor(newOnTouchListener(){
  56. @Override
  57. publicbooleanonTouch(Viewv,MotionEventevent){
  58. if(event.getAction()==MotionEvent.ACTION_OUTSIDE){
  59. QuickActions.this.window.dismiss();
  60. returntrue;
  61. }
  62. returnfalse;
  63. }
  64. });
  65. //得到一个windowManager对象,用来得到窗口的一些属性
  66. windowManager=(WindowManager)anchor.getContext().getSystemService(Context.WINDOW_SERVICE);
  67. actionList=newArrayList<ActionItem>();
  68. context=anchor.getContext();
  69. inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  70. //装载布局,root即为弹出窗口的布局
  71. root=(ViewGroup)inflater.inflate(R.layout.quickaction,null);
  72. //得到上下两个箭头
  73. mArrowDown=(ImageView)root.findViewById(R.id.arrow_down);
  74. mArrowUp=(ImageView)root.findViewById(R.id.arrow_up);
  75. setContentView(root);
  76. mTrackAnim=AnimationUtils.loadAnimation(anchor.getContext(),R.anim.rail);
  77. //设置动画的加速效果
  78. mTrackAnim.setInterpolator(newInterpolator(){
  79. publicfloatgetInterpolation(floatt){
  80. finalfloatinner=(t*1.55f)-1.1f;
  81. return1.2f-inner*inner;
  82. }
  83. });
  84. //这个是弹出窗口内的水平布局
  85. mTrack=(ViewGroup)root.findViewById(R.id.tracks);
  86. animStyle=ANIM_AUTO;//设置动画风格
  87. animateTrack=true;
  88. }
  89. /**
  90. *设置一个flag来标识动画显示
  91. */
  92. publicvoidanimateTrack(booleananimateTrack){
  93. this.animateTrack=animateTrack;
  94. }
  95. /**
  96. *设置动画风格
  97. */
  98. publicvoidsetAnimStyle(intanimStyle){
  99. this.animStyle=animStyle;
  100. }
  101. /**
  102. *增加一个action
  103. */
  104. publicvoidaddActionItem(ActionItemaction){
  105. actionList.add(action);
  106. }
  107. /**
  108. *弹出弹窗
  109. */
  110. publicvoidshow(){
  111. //预处理,设置window
  112. preShow();
  113. int[]location=newint[2];
  114. //得到anchor的位置
  115. anchor.getLocationOnScreen(location);
  116. //以anchor的位置构造一个矩形
  117. RectanchorRect=newRect(location[0],location[1],location[0]+anchor.getWidth(),location[1]
  118. +anchor.getHeight());
  119. root.setLayoutParams(newLayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
  120. root.measure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
  121. introotWidth=root.getMeasuredWidth();
  122. introotHeight=root.getMeasuredHeight();
  123. //得到屏幕的宽
  124. intscreenWidth=windowManager.getDefaultDisplay().getWidth();
  125. //设置弹窗弹出的位置的x/y
  126. intxPos=(screenWidth-rootWidth)/2;
  127. intyPos=anchorRect.top-rootHeight;
  128. booleanonTop=true;
  129. //在底部弹出
  130. if(rootHeight>anchorRect.top){
  131. yPos=anchorRect.bottom;
  132. onTop=false;
  133. }
  134. //根据弹出位置,设置不同方向箭头图片
  135. showArrow(((onTop)?R.id.arrow_down:R.id.arrow_up),anchorRect.centerX());
  136. //设置弹出动画风格
  137. setAnimationStyle(screenWidth,anchorRect.centerX(),onTop);
  138. //创建actionlist
  139. createActionList();
  140. //在指定位置弹出弹窗
  141. window.showAtLocation(this.anchor,Gravity.NO_GRAVITY,xPos,yPos);
  142. //设置弹窗内部的水平布局的动画
  143. if(animateTrack)mTrack.startAnimation(mTrackAnim);
  144. }
  145. /**
  146. *预处理窗口
  147. */
  148. protectedvoidpreShow(){
  149. if(root==null){
  150. thrownewIllegalStateException("需要为弹窗设置布局");
  151. }
  152. //背景是唯一能确定popupwindow宽高的元素,这里使用root的背景,但是需要给popupwindow设置一个空的BitmapDrawable
  153. if(background==null){
  154. window.setBackgroundDrawable(newBitmapDrawable());
  155. }else{
  156. window.setBackgroundDrawable(background);
  157. }
  158. window.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
  159. window.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
  160. window.setTouchable(true);
  161. window.setFocusable(true);
  162. window.setOutsideTouchable(true);
  163. //指定布局
  164. window.setContentView(root);
  165. }
  166. /**
  167. *设置动画风格
  168. *
  169. *@paramscreenWidth屏幕宽底
  170. *@paramrequestedX距离屏幕左边的距离
  171. *@paramonTop一个flag用来标识窗口的显示位置,如果为true则显示在anchor的顶部
  172. */
  173. privatevoidsetAnimationStyle(intscreenWidth,intrequestedX,booleanonTop){
  174. //取得屏幕左边到箭头中心的位置
  175. intarrowPos=requestedX-mArrowUp.getMeasuredWidth()/2;
  176. //根据animStyle设置相应动画风格
  177. switch(animStyle){
  178. caseANIM_GROW_FROM_LEFT:
  179. window.setAnimationStyle((onTop)?R.style.Animations_PopUpMenu_Left:R.style.Animations_PopDownMenu_Left);
  180. break;
  181. caseANIM_GROW_FROM_RIGHT:
  182. window.setAnimationStyle((onTop)?R.style.Animations_PopUpMenu_Right:R.style.Animations_PopDownMenu_Right);
  183. break;
  184. caseANIM_GROW_FROM_CENTER:
  185. window.setAnimationStyle((onTop)?R.style.Animations_PopUpMenu_Center:R.style.Animations_PopDownMenu_Center);
  186. break;
  187. caseANIM_AUTO:
  188. if(arrowPos<=screenWidth/4){
  189. window.setAnimationStyle((onTop)?R.style.Animations_PopUpMenu_Left:R.style.Animations_PopDownMenu_Left);
  190. }elseif(arrowPos>screenWidth/4&&arrowPos<3*(screenWidth/4)){
  191. window.setAnimationStyle((onTop)?R.style.Animations_PopUpMenu_Center:R.style.Animations_PopDownMenu_Center);
  192. }else{
  193. window.setAnimationStyle((onTop)?R.style.Animations_PopDownMenu_Right:R.style.Animations_PopDownMenu_Right);
  194. }
  195. break;
  196. }
  197. }
  198. /**
  199. *创建actionlist
  200. */
  201. privatevoidcreateActionList(){
  202. Viewview;
  203. Stringtitle;
  204. Drawableicon;
  205. OnClickListenerlistener;
  206. intindex=1;
  207. for(inti=0;i<actionList.size();i++){
  208. title=actionList.get(i).getTitle();
  209. icon=actionList.get(i).getIcon();
  210. listener=actionList.get(i).getListener();
  211. //得到actionitem
  212. view=getActionItem(title,icon,listener);
  213. view.setFocusable(true);
  214. view.setClickable(true);
  215. //将其加入布局
  216. mTrack.addView(view,index);
  217. index++;
  218. }
  219. }
  220. /**
  221. *获得actionitem
  222. *
  223. *@paramtitleaction的标题
  224. *@paramiconaction的图标
  225. *@paramlisteneraction的点击事件监听器
  226. *@returnaction的item
  227. */
  228. privateViewgetActionItem(Stringtitle,Drawableicon,OnClickListenerlistener){
  229. //装载action布局
  230. LinearLayoutcontainer=(LinearLayout)inflater.inflate(R.layout.action_item,null);
  231. ImageViewimg=(ImageView)container.findViewById(R.id.icon);
  232. TextViewtext=(TextView)container.findViewById(R.id.title);
  233. if(icon!=null){
  234. img.setImageDrawable(icon);
  235. }else{
  236. img.setVisibility(View.GONE);
  237. }
  238. if(title!=null){
  239. text.setText(title);
  240. }else{
  241. text.setVisibility(View.GONE);
  242. }
  243. if(listener!=null){
  244. container.setOnClickListener(listener);
  245. }
  246. returncontainer;
  247. }
  248. /**
  249. *显示箭头
  250. *
  251. *@param箭头资源id
  252. *@param距离屏幕左边的距离
  253. */
  254. privatevoidshowArrow(intwhichArrow,intrequestedX){
  255. finalViewshowArrow=(whichArrow==R.id.arrow_up)?mArrowUp:mArrowDown;
  256. finalViewhideArrow=(whichArrow==R.id.arrow_up)?mArrowDown:mArrowUp;
  257. finalintarrowWidth=mArrowUp.getMeasuredWidth();
  258. showArrow.setVisibility(View.VISIBLE);
  259. ViewGroup.MarginLayoutParamsparam=(ViewGroup.MarginLayoutParams)showArrow.getLayoutParams();
  260. //以此设置距离左边的距离
  261. param.leftMargin=requestedX-arrowWidth/2;
  262. hideArrow.setVisibility(View.INVISIBLE);
  263. }
  264. }

有点长,不过注释都写的很清楚了。show()方法完成窗口的弹出。里面调用其他方法设置了窗口弹出的位置,设置了相应的动画弹出风格和箭头朝向以及位置,创建了action item。大家可以从这个方法里开始看,看每个的实现。

最后写个测试类。放一个Button在屏幕顶部,一个在屏幕底部。点击弹出弹窗。

        
  1. packagecom.notice.quickaction;
  2. importandroid.app.Activity;
  3. importandroid.os.Bundle;
  4. importandroid.view.View;
  5. importandroid.view.View.OnClickListener;
  6. importandroid.widget.Button;
  7. importandroid.widget.Toast;
  8. /**
  9. *实现activity
  10. */
  11. publicclassMyQuickextendsActivity{
  12. @Override
  13. publicvoidonCreate(BundlesavedInstanceState){
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.main);
  16. //得到一个actionItem对象
  17. finalActionItemchart=newActionItem();
  18. //设置标题,图标,点击事件
  19. chart.setTitle("Chart");
  20. chart.setIcon(getResources().getDrawable(R.drawable.chart));
  21. chart.setOnClickListener(newOnClickListener(){
  22. @Override
  23. publicvoidonClick(Viewv){
  24. Toast.makeText(MyQuick.this,"Chartselected",Toast.LENGTH_SHORT).show();
  25. }
  26. });
  27. finalActionItemproduction=newActionItem();
  28. production.setTitle("Products");
  29. production.setIcon(getResources().getDrawable(R.drawable.production));
  30. production.setOnClickListener(newOnClickListener(){
  31. @Override
  32. publicvoidonClick(Viewv){
  33. Toast.makeText(MyQuick.this,"Productsselected",Toast.LENGTH_SHORT).show();
  34. }
  35. });
  36. Buttonbtn1=(Button)this.findViewById(R.id.btn1);
  37. //点击按钮弹出
  38. btn1.setOnClickListener(newView.OnClickListener(){
  39. @Override
  40. publicvoidonClick(Viewv){
  41. //初始化一个QuickActions
  42. QuickActionsqa=newQuickActions(v);
  43. //为他添加actionitem
  44. qa.addActionItem(chart);
  45. qa.addActionItem(production);
  46. qa.addActionItem(production);
  47. qa.addActionItem(production);
  48. //设置动画风格
  49. qa.setAnimStyle(QuickActions.ANIM_AUTO);
  50. qa.show();
  51. }
  52. });
  53. finalActionItemdashboard=newActionItem();
  54. dashboard.setIcon(getResources().getDrawable(R.drawable.dashboard));
  55. dashboard.setOnClickListener(newOnClickListener(){
  56. @Override
  57. publicvoidonClick(Viewv){
  58. Toast.makeText(MyQuick.this,"dashboardselected",Toast.LENGTH_SHORT).show();
  59. }
  60. });
  61. finalActionItemusers=newActionItem();
  62. users.setIcon(getResources().getDrawable(R.drawable.users));
  63. users.setOnClickListener(newOnClickListener(){
  64. @Override
  65. publicvoidonClick(Viewv){
  66. Toast.makeText(MyQuick.this,"Productsselected",Toast.LENGTH_SHORT).show();
  67. }
  68. });
  69. Buttonbtn2=(Button)this.findViewById(R.id.btn2);
  70. btn2.setOnClickListener(newOnClickListener(){
  71. @Override
  72. publicvoidonClick(Viewv){
  73. QuickActionsqa=newQuickActions(v);
  74. qa.addActionItem(dashboard);
  75. qa.addActionItem(users);
  76. qa.setAnimStyle(QuickActions.ANIM_GROW_FROM_CENTER);
  77. qa.show();
  78. }
  79. });
  80. }
  81. }

再讲下PopupWindow的风格的实现。其中一个风格代码如下:

        
  1. <stylename="Animations.PopDownMenu.Left">
  2. <itemname="@android:windowEnterAnimation">@anim/grow_from_topleft_to_bottomright</item>
  3. <itemname="@android:windowExitAnimation">@anim/shrink_from_bottomright_to_topleft</item>
  4. </style>

写两个item,分别实现弹出和消失动画。因为篇幅有限(好像已经很长了。。。),就不全部贴出来了。动画都是一个scale加一个alpha,对动画不熟悉的朋友可以自己研究下,从底部弹出的动画文件grow_from_bottom.xml:

        
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <setxmlns:android="http://schemas.android.com/apk/res/android">
  3. <scale
  4. android:fromXScale="0.3"android:toXScale="1.0"
  5. android:fromYScale="0.3"android:toYScale="1.0"
  6. android:pivotX="50%"android:pivotY="100%"
  7. android:duration="@android:integer/config_shortAnimTime"
  8. />
  9. <alpha
  10. android:interpolator="@android:anim/decelerate_interpolator"
  11. android:fromAlpha="0.0"android:toAlpha="1.0"
  12. android:duration="@android:integer/config_shortAnimTime"
  13. />
  14. </set>

最后来看看实现效果:

好了 希望大家喜欢 有问题可以留言交流~

更多相关文章

  1. android布局中的基本属性:
  2. Delphi XE5 for Android(安卓)(十一)
  3. Android制作并替换系统开机动画bootanimation.zip(需root)
  4. android切换屏幕时的生命周期
  5. android 模拟器命令 附:模拟器不能联网设置
  6. Android(安卓)中Popwindow弹出菜单的两种方法实例
  7. Android控件_ProgressBar使用
  8. Android(安卓)搜索框:SearchView 的属性和用法详解
  9. Android怎么在控制台输出语句

随机推荐

  1. Android打开关闭触摸提示音
  2. android camera调试命令
  3. Android学习备忘022——FBReader源码解析
  4. 优化布局在Android–减少过度渲染
  5. 7.1 DropTargetBar style
  6. Android调用Jni,非常简单的一个Demo
  7. Android中系统设置参数改变监听(以时间同
  8. Android(安卓)spinner 动态添加时报错 ja
  9. android 代码片段
  10. 学习笔记 Android(安卓)使用AIDL实现进程