android UI进阶之弹窗的使用(2)--实现通讯录的弹窗效果

[来源] 达内    [编辑] 达内   [时间]2012-09-17

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

android中提供了QuickContactBadge来实现这一效果。这里简单演示下。
首先创建布局文件:
[html] view plaincopy
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns: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中配置:
[java] view plaincopy
1. public class QuickcontactActivity extends Activity {
2.
3. /** Called when the activity is first created. */
4. @Override
5. public void onCreate(Bundle savedInstanceState) {
6. super.onCreate(savedInstanceState);
7. setContentView(R.layout.main);
8.
9. QuickContactBadge smallBadge = (QuickContactBadge) findViewById(.badge);
10. // 从email关联一个contact
11. smallBadge.assignContactFromEmail("", true);
12. // 设置窗口模式
13. smallBadge.setMode(ContactsContract.QuickContact.MODE_SMALL);
14. }
15. }

注意加入读通讯录的权限
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>  

但是这个组件局限性很大,弹出窗口中只能是一些contact操作。但是仔细一想,这样的操作并不难,不就是一个带动画的弹窗么。下面就来我们自己实现一个。
实现一个带动画的弹窗并不难,在我的之前一篇博客中有讲过弹窗PopupWindow的使用,不清楚弹窗的朋友可以去看下。在这里实现的难点主要有这些:
1.判断基准view在屏幕中的位置,从而确定弹窗弹出的位置以及动画。这是非常重要的一点,或许基准在屏幕上方,那么就要向下弹出。
2.动态的添加弹窗中的按钮,并实现点击
3.箭头位置的控制。箭头应该保持在基准的下方。
4.动画的匹配。里面有两种动画。一种是PopupWindow弹出动画,我们通过设置弹窗的style来实现(style的用法可以参考我之前的博客)。另一种是弹窗中间的布局的动画。
了解了难点以后,写起来就方便了。
首先实现弹窗的布局:
[html] view plaincopy
1. <?xml version="1.0" encoding="utf-8"?>
2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3. android:layout_width="wrap_content"
4. android:layout_height="wrap_content">
5.
6. <FrameLayout
7. android:layout_marginTop="10dip"
8. android:id="@+id/header2"
9. android:layout_width="fill_parent"
10. android:layout_height="wrap_content"
11. android:background="@drawable/quickcontact_top_frame"/>
12.
13. <ImageView
14. android:id="@+id/arrow_up"
15. android:layout_width="wrap_content"
16. android:layout_height="wrap_content"
17. android:src="@drawable/quickcontact_arrow_up" />
18.
19. <HorizontalScrollView
20. android:id="@+id/scroll"
21. android:layout_width="fill_parent"
22. android:layout_height="wrap_content"
23. android:fadingEdgeLength="0dip"
24. android:layout_below="@id/header2"
25. android:background="@drawable/quickcontact_slider_background"
26. android:scrollbars="none">
27.
28. <LinearLayout
29. android:id="@+id/tracks"
30. android:layout_width="wrap_content"
31. android:layout_height="wrap_content"
32. android:paddingTop="4dip"
33. android:paddingBottom="4dip"
34. android:orientation="horizontal">
35.
36. <ImageView
37. android:layout_width="wrap_content"
38. android:layout_height="wrap_content"
39. android:src="@drawable/quickcontact_slider_grip_left" />
40.
41. <ImageView
42. android:layout_width="wrap_content"
43. android:layout_height="wrap_content"
44. android:src="@drawable/quickcontact_slider_grip_right" />
45.
46. </LinearLayout>
47.
48. </HorizontalScrollView>
49.
50. <FrameLayout
51. android:id="@+id/footer"
52. android:layout_width="fill_parent"
53. android:layout_height="wrap_content"
54. android:layout_below="@id/scroll"
55. android:background="@drawable/quickcontact_bottom_frame" />
56.
57. <ImageView
58. android:id="@+id/arrow_down"
59. android:layout_width="wrap_content"
60. android:layout_height="wrap_content"
61. android:layout_marginTop="-1dip"
62. android:layout_below="@id/footer"
63. android:src="@drawable/quickcontact_arrow_down" />
64.
65. </RelativeLayout>

窗体内部使用一个HorizontalScrollView可以实现一个滑动效果。我们可以动态的在这个布局中添加按钮,我们称作Actionitem。
写一个ActionItem类,使得我们可以用一个ArrayList做容器,动态的添加这些actionitem。这些都是服务于第二个难点。
[java] view plaincopy
1. package com.notice.quickaction;
2.
3. import .drawable.Drawable;
4. import android.view.View.OnClickListener;
5.
6. /**
7. * Action item, 每个item里面都有一个ImageView和一个TextView
8. */
9. public class ActionItem {
10.
11. private Drawable icon;
12. private String title;
13. private OnClickListener listener;
14.
15. /**
16. * 构造器
17. */
18. public ActionItem() {
19. }
20.
21. /**
22. * 带Drawable参数的构造器
23. */
24. public ActionItem(Drawable icon) {
25. this.icon = icon;
26. }
27.
28. /**
29. * 设置标题
30. */
31. public void setTitle(String title) {
32. this.title = title;
33. }
34.
35. /**
36. * 获得标题
37. *
38. * @return action title
39. */
40. public String getTitle() {
41. return this.title;
42. }
43.
44. /**
45. * 设置图标
46. */
47. public void setIcon(Drawable icon) {
48. this.icon = icon;
49. }
50.
51. /**
52. * 获得图标
53. */
54. public Drawable getIcon() {
55. return this.icon;
56. }
57.
58. /**
59. * 绑定监听器
60. */
61. public void setOnClickListener(OnClickListener listener) {
62. this.listener = listener;
63. }
64.
65. /**
66. * 获得监听器
67. */
68. public OnClickListener getListener() {
69. return this.listener;
70. }
71. }

接下来就是这个弹窗的实现了,我们继承PopupWindow类。在这个类中我们需要实现通过位置设置动画及弹出位置,并且给出一个方法供实现类调用,来动态添加item和设置动画效果。
代码如下:
[java] view plaincopy
1. /**
2. * 继承弹窗,构造我们需要的弹窗
3. */
4. public class QuickActions extends PopupWindow {
5.
6. private final View root;
7. private final ImageView mArrowUp;
8. private final ImageView mArrowDown;
9. private final Animation mTrackAnim;
10. private final LayoutInflater inflater;
11. private final Context context;
12.
13. protected final View anchor;
14. protected final PopupWindow window;
15. private Drawable background = null;
16. protected final WindowManager windowManager;
17.
18. protected static final int ANIM_GROW_FROM_LEFT = 1;
19. protected static final int ANIM_GROW_FROM_RIGHT = 2;
20. protected static final int ANIM_GROW_FROM_CENTER = 3;
21. protected static final int ANIM_AUTO = 4;
22.
23. private int animStyle;
24. private boolean animateTrack;
25. private ViewGroup mTrack;
26. private ArrayList<ActionItem> actionList;
27.
28. /**
29. * 构造器,在这里初始化一些内容
30. *
31. * @param anchor 像我之前博客所说的理解成一个基准 弹窗以此为基准弹出
32. */
33. public QuickActions(View anchor) {
34. super(anchor);
35.
36. this.anchor = anchor;
37. this.window = new PopupWindow(anchor.getContext());
38.
39. // 在popwindow外点击即关闭该window
40. window.setTouchInterceptor(new OnTouchListener() {
41.
42. @Override
43. public boolean onTouch(View v, MotionEvent event) {
44. if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
45. QuickActions.this.window.dismiss();
46.
47. return true;
48. }
49.
50. return false;
51. }
52. });
53.
54. // 得到一个windowManager对象,用来得到窗口的一些属性
55. windowManager = (WindowManager) anchor.getContext().getSystemService(Context.WINDOW_SERVICE);
56.
57. actionList = new ArrayList<ActionItem>();
58. context = anchor.getContext();
59. inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
60.
61. // 装载布局,root即为弹出窗口的布局
62. root = (ViewGroup) inflater.inflate(R.layout.quickaction, null);
63.
64. // 得到上下两个箭头
65. mArrowDown = (ImageView) root.findViewById(.arrow_down);
66. mArrowUp = (ImageView) root.findViewById(.arrow_up);
67.
68. setContentView(root);
69.
70. mTrackAnim = AnimationUtils.loadAnimation(anchor.getContext(), R.anim.rail);
71.
72. // 设置动画的加速效果
73. mTrackAnim.setInterpolator(new Interpolator() {
74.
75. public float getInterpolation(float t) {
76.
77. final float inner = (t * 1.55f) - 1.1f;
78.
79. return 1.2f - inner * inner;
80. }
81. });
82.
83. // 这个是弹出窗口内的水平布局
84. mTrack = (ViewGroup) root.findViewById(.tracks);
85. animStyle = ANIM_AUTO;// 设置动画风格
86. animateTrack = true;
87. }
88.
89. /**
90. * 设置一个flag来标识动画显示
91. */
92. public void animateTrack(boolean animateTrack) {
93. this.animateTrack = animateTrack;
94. }
95.
96. /**
97. * 设置动画风格
98. */
99. public void setAnimStyle(int animStyle) {
100. this.animStyle = animStyle;
101. }
102.
103. /**
104. * 增加一个action
105. */
106. public void addActionItem(ActionItem action) {
107. actionList.add(action);
108. }
109.
110. /**
111. * 弹出弹窗
112. */
113. public void show() {
114. // 预处理,设置window
115. preShow();
116.
117. int[] location = new int[2];
118. // 得到anchor的位置
119. anchor.getLocationOnScreen(location);
120.
121. // 以anchor的位置构造一个矩形
122. Rect anchorRect = new Rect(location[0], location[1], location[0] + anchor.getWidth(), location[1]
123. + anchor.getHeight());
124.
125. root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
126. root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
127.
128. int rootWidth = root.getMeasuredWidth();
129. int rootHeight = root.getMeasuredHeight();
130.
131. // 得到屏幕的宽
132. int screenWidth = windowManager.getDefaultDisplay().getWidth();
133.
134. // 设置弹窗弹出的位置的x/y
135. int xPos = (screenWidth - rootWidth) / 2;
136. int yPos = - rootHeight;
137.
138. boolean onTop = true;
139.
140. // 在底部弹出
141. if (rootHeight > ) {
142. yPos = anchorRect.bottom;
143. onTop = false;
144. }
145.
146. // 根据弹出位置,设置不同方向箭头图片
147. showArrow(((onTop) ? .arrow_down : .arrow_up), anchorRect.centerX());
148.
149. // 设置弹出动画风格
150. setAnimationStyle(screenWidth, anchorRect.centerX(), onTop);
151.
152. // 创建action list
153. createActionList();
154.
155. // 在指定位置弹出弹窗
156. window.showAtLocation(this.anchor, Gravity.NO_GRAVITY, xPos, yPos);
157.
158. // 设置弹窗内部的水平布局的动画
159. if (animateTrack) mTrack.startAnimation(mTrackAnim);
160. }
161.
162. /**
163. * 预处理窗口
164. */
165. protected void preShow() {
166. if (root == null) {
167. throw new IllegalStateException("需要为弹窗设置布局");
168. }
169.
170. // 背景是唯一能确定popupwindow宽高的元素,这里使用root的背景,但是需要给popupwindow设置一个空的BitmapDrawable
171. if (background == null) {
172. window.setBackgroundDrawable(new BitmapDrawable());
173. } else {
174. window.setBackgroundDrawable(background);
175. }
176.
177. window.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
178. window.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
179. window.setTouchable(true);
180. window.setFocusable(true);
181. window.setOutsideTouchable(true);
182. // 指定布局
183. window.setContentView(root);
184. }
185.
186. /**
187. * 设置动画风格
188. *
189. * @param screenWidth 屏幕宽底
190. * @param requestedX 距离屏幕左边的距离
191. * @param onTop 一个flag用来标识窗口的显示位置,如果为true则显示在anchor的顶部
192. */
193. private void setAnimationStyle(int screenWidth, int requestedX, boolean onTop) {
194. // 取得屏幕左边到箭头中心的位置
195. int arrowPos = requestedX - mArrowUp.getMeasuredWidth() / 2;
196. // 根据animStyle设置相应动画风格
197. switch (animStyle) {
198. case ANIM_GROW_FROM_LEFT:
199. window.setAnimationStyle((onTop) ? .Animations_PopUpMenu_Left : .Animations_PopDownMenu_Left);
200. break;
201.
202. case ANIM_GROW_FROM_RIGHT:
203. window.setAnimationStyle((onTop) ? .Animations_PopUpMenu_Right : .Animations_PopDownMenu_Right);
204. break;
205.
206. case ANIM_GROW_FROM_CENTER:
207. window.setAnimationStyle((onTop) ? .Animations_PopUpMenu_Center : .Animations_PopDownMenu_Center);
208. break;
209.
210. case ANIM_AUTO:
211. if (arrowPos <= screenWidth / 4) {
212. window.setAnimationStyle((onTop) ? .Animations_PopUpMenu_Left : .Animations_PopDownMenu_Left);
213. } else if (arrowPos > screenWidth / 4 && arrowPos < 3 * (screenWidth / 4)) {
214. window.setAnimationStyle((onTop) ? .Animations_PopUpMenu_Center : .Animations_PopDownMenu_Center);
215. } else {
216. window.setAnimationStyle((onTop) ? .Animations_PopDownMenu_Right : .Animations_PopDownMenu_Right);
217. }
218.
219. break;
220. }
221. }
222.
223. /**
224. * 创建action list
225. */
226. private void createActionList() {
227. View view;
228. String title;
229. Drawable icon;
230. OnClickListener listener;
231. int index = 1;
232.
233. for (int i = 0; i < actionList.size(); i++) {
234. title = actionList.get(i).getTitle();
235. icon = actionList.get(i).getIcon();
236. listener = actionList.get(i).getListener();
237. // 得到action item
238. view = getActionItem(title, icon, listener);
239.
240. view.setFocusable(true);
241. view.setClickable(true);
242.
243. // 将其加入布局
244. mTrack.addView(view, index);
245.
246. index++;
247. }
248. }
249.
250. /**
251. * 获得 action item
252. *
253. * @param title action的标题
254. * @param icon action的图标
255. * @param listener action的点击事件监听器
256. * @return action的item
257. */
258. private View getActionItem(String title, Drawable icon, OnClickListener listener) {
259. // 装载action布局
260. LinearLayout container = (LinearLayout) inflater.inflate(R.layout.action_item, null);
261. ImageView img = (ImageView) container.findViewById(.icon);
262. TextView text = (TextView) container.findViewById(.title);
263.
264. if (icon != null) {
265. img.setImageDrawable(icon);
266. } else {
267. img.setVisibility(View.GONE);
268. }
269.
270. if (title != null) {
271. text.setText(title);
272. } else {
273. text.setVisibility(View.GONE);
274. }
275.
276. if (listener != null) {
277. container.setOnClickListener(listener);
278. }
279.
280. return container;
281. }
282.
283. /**
284. * 显示箭头
285. *
286. * @param 箭头资源id
287. * @param 距离屏幕左边的距离
288. */
289. private void showArrow(int whichArrow, int requestedX) {
290. final View showArrow = (whichArrow == .arrow_up) ? mArrowUp : mArrowDown;
291. final View hideArrow = (whichArrow == .arrow_up) ? mArrowDown : mArrowUp;
292.
293. final int arrowWidth = mArrowUp.getMeasuredWidth();
294.
295. showArrow.setVisibility(View.VISIBLE);
296.
297. ViewGroup.MarginLayoutParams param = (ViewGroup.MarginLayoutParams) showArrow.getLayoutParams();
298.
299. // 以此设置距离左边的距离
300. param.leftMargin = requestedX - arrowWidth / 2;
301.
302. hideArrow.setVisibility(View.INVISIBLE);
303. }
304.
305. }

 有点长,不过注释都写的很清楚了。show()方法完成窗口的弹出。里面调用其他方法设置了窗口弹出的位置,设置了相应的动画弹出风格和箭头朝向以及位置,创建了action item。大家可以从这个方法里开始看,看每个的实现。
最后写个测试类。放一个Button在屏幕顶部,一个在屏幕底部。点击弹出弹窗。
[java] view plaincopy
1. package com.notice.quickaction;
2.
3. import .Activity;
4. import android.os.Bundle;
5. import android.view.View;
6. import android.view.View.OnClickListener;
7. import android.widget.Button;
8. import android.widget.Toast;
9.
10. /**
11. * 实现activity
12. */
13. public class MyQuick extends Activity {
14.
15. @Override
16. public void onCreate(Bundle savedInstanceState) {
17. super.onCreate(savedInstanceState);
18.
19. setContentView(R.layout.main);
20.
21. // 得到一个actionItem对象
22. final ActionItem chart = new ActionItem();
23.
24. // 设置标题,图标,点击事件
25. chart.setTitle("Chart");
26. chart.setIcon(getResources().getDrawable(R.drawable.chart));
27. chart.setOnClickListener(new OnClickListener() {
28.
29. @Override
30. public void onClick(View v) {
31. Toast.makeText(MyQuick.this, "Chart selected", Toast.LENGTH_SHORT).show();
32. }
33. });
34.
35. final ActionItem production = new ActionItem();
36.
37. production.setTitle("Products");
38. production.setIcon(getResources().getDrawable(R.drawable.production));
39. production.setOnClickListener(new OnClickListener() {
40.
41. @Override
42. public void onClick(View v) {
43. Toast.makeText(MyQuick.this, "Products selected", Toast.LENGTH_SHORT).show();
44. }
45. });
46.
47. Button btn1 = (Button) this.findViewById(.btn1);
48. // 点击按钮弹出
49. btn1.setOnClickListener(new View.OnClickListener() {
50.
51. @Override
52. public void onClick(View v) {
53. // 初始化一个QuickActions
54. QuickActions qa = new QuickActions(v);
55. // 为他添加actionitem
56. qa.addActionItem(chart);
57. qa.addActionItem(production);
58. qa.addActionItem(production);
59. qa.addActionItem(production);
60. // 设置动画风格
61. qa.setAnimStyle(QuickActions.ANIM_AUTO);
62.
63. ();
64. }
65. });
66.
67. final ActionItem dashboard = new ActionItem();
68.
69. dashboard.setIcon(getResources().getDrawable(R.drawable.dashboard));
70. dashboard.setOnClickListener(new OnClickListener() {
71.
72. @Override
73. public void onClick(View v) {
74. Toast.makeText(MyQuick.this, "dashboard selected", Toast.LENGTH_SHORT).show();
75. }
76. });
77.
78. final ActionItem users = new ActionItem();
79.
80. users.setIcon(getResources().getDrawable(R.drawable.users));
81. users.setOnClickListener(new OnClickListener() {
82.
83. @Override
84. public void onClick(View v) {
85. Toast.makeText(MyQuick.this, "Products selected", Toast.LENGTH_SHORT).show();
86. }
87. });
88.
89. Button btn2 = (Button) this.findViewById(.btn2);
90. btn2.setOnClickListener(new OnClickListener() {
91.
92. @Override
93. public void onClick(View v) {
94. QuickActions qa = new QuickActions(v);
95.
96. qa.addActionItem(dashboard);
97. qa.addActionItem(users);
98. qa.setAnimStyle(QuickActions.ANIM_GROW_FROM_CENTER);
99.
100. ();
101. }
102. });
103. }
104. }

再讲下PopupWindow的风格的实现。其中一个风格代码如下:
[html] view plaincopy
1. <style name="Animations.PopDownMenu.Left">
2. <item name="@android:windowEnterAnimation">@anim/grow_from_topleft_to_bottomright</item>
3. <item name="@android:windowExitAnimation">@anim/shrink_from_bottomright_to_topleft</item>
4. </style>
写两个item,分别实现弹出和消失动画。因为篇幅有限(好像已经很长了。。。),就不全部贴出来了。动画都是一个scale加一个alpha,对动画不熟悉的朋友可以自己研究下,从底部弹出的动画文件grow_from_bottom.xml:
[html] view plaincopy
1. <?xml version="1.0" encoding="utf-8"?>
2. <set xmlns: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>
 

资源下载