閱讀373 返回首頁    go 阿裏雲 go 技術社區[雲棲]


QuickContact分析及其彈出窗口實現

https://winuxxan.blog.51cto.com/2779763/518044

一、簡介
QuickContact是為了應用程序能夠快速方便的訪問聯係人,並且快速的運用聯係人的信息執行相應操作而設計的。最常見的在Contacts應用程序中如下圖所示:

在Activity中存在一個圖標,點擊該圖標後彈出一個窗口,窗口中會有幾個圖標,不同的圖標表示針對該聯係人進行的不同操作,比如打電話,發短信,發送郵件,進入主頁等等。圖標的顯示和不顯示取決於該聯係人是否存在該種操作相關的信息。比如,如果該聯係人中如果存在郵箱的話,就可以出現發送郵件的圖標,否則就不會出現。


二、在自己的應用程序中應用QuickContact
在自己的程序中加入QuickContact十分方便,可以用Framework中的組件QuickContactBadge。
比如,我們創建一個Activity,設置它的layout如下:


  1. <LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
  2. android:orientation="vertical"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. >
  6. <QuickContactBadge
  7. android:id="@+id/badge_small"
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:src="@drawable/icon">
  11. </QuickContactBadge>
  12. </LinearLayout>

Activity的onCreate代碼如下:


  1. QuickContactBadge smallBadge = (QuickContactBadge) findViewById(R.id.badge_small);
  2. smallBadge.assignContactFromEmail("winuxxan@gmail.com", true);
  3. smallBadge.setMode(ContactsContract.QuickContact.MODE_LARGE);

主要還有一點,要在AndroidManifest中設置android.permission.READ_CONTACTS的權限,否則,不會彈出窗口,而是直接進入Contacts中的聯係人詳情界麵。
運行效果如圖:

由於本文不是針對QuickContactBadge的詳細講解,而是講解它的實現方法,從而能夠見賢思齊,設計出類似的功能。要了解QuickContactBadge的詳細用法可以看Android文檔,和google。


三、結構
係統在實現QuickContact包含三部分:
1、彈出界麵。
2、ContactContract中的調用接口
3、組件QuickContactBadge。
彈出界麵在應用程序Contacts中編寫,這也是QuickContact的主要部分。
ContactContract為方便使用QuickContact定義了幾個函數,可以在不同的情形下顯示出QuickContact。
QuickContactBadge是為了方便開發者使用QuickContact,設計的一個組件,開發者隻需要在自己的Layout中加入該組件,便可以方便的使用。而且該組件還支持根據郵箱、電話號碼等指定某個聯係人。實際上,QuickContactBadge在單擊的時候調用了ContactContract中的函數來顯示QuickContact。因此,實際上,ContactContract中的QuickContact可以完成更多情況下QuickContact的顯示。

框圖待補充

四、QuickContactActivity和QuickContactWindow
為了使彈出窗口可以跨進程共享,彈出界麵的實現實際上是采用了透明Activity貼上一層View的方式。透明的Activity無疑就是QuickContactActivity了,在該Activity中含有一個QuickContactWindow的成員,通過QuickContactWindow將一個View貼到該Activity上。
那麼,QuickContactWindow是如何將View貼到Activity上的呢?大家需要了解Activity,View,Window和WindowManager之間的關係,不明白的趕緊查下資料。
QuickContactWindow在創建時就會創建一個PhoneWindow,然後設置它的布局,進行一係列的初始化,在顯示的時候,會查詢該聯係人的信息,查詢完畢後生成該聯係人的視圖,之後獲得PhoneWindow的根節點,將該根節點加到WindowManager中,該View就會被貼到Activity中去了。


五、ContactContract中的QuickContact
ContactContract中的QuickContact的顯示函數,實際上就是用Intent開啟了QuickContactActivity。


六、QuickContactBadge
QuickContactBadge繼承自ImageView,所以所有ImageView的函數都可以用來設置QuickContactBadge。當點擊QuickContactBadge時,它調用了ContactContract中的QuickContact顯示函數。
為了為開發者提供方便,QuickContactBadge可以根據開發者提供的郵箱或電話號碼等信息找到該聯係人。


七、見賢思齊
1、PopupWindow彈出窗口實現
其實,單純實現彈出窗口是很簡單的,Android為我們提供了PopupWindow這個組件。通過這個組件我們可以將我們的窗口顯示在頂層,並且可以通過坐標來決定它的位置。如下麵的代碼:


  1. private void showPopupWindow(int x, int y, int width, int height) {
  2. TextView textView = new TextView(this);
  3. textView.setText("Hello popupWindow");
  4. textView.setBackgroundColor(Color.CYAN);
  5. PopupWindow popupWindow = new PopupWindow(textView, width, height);
  6. popupWindow.showAtLocation(getWindow().getDecorView(), Gravity.NO_GRAVITY, x, y);
  7. }

顯示效果如下:

需要注意的一點就是,PopupWindow在Activity的onCreate函數中顯示是會出現錯誤的,用戶可以自行驗證。解決方法不是本文暫不研究。


2、Dialog的彈出窗口實現
我們常見的Dialog都是居中顯示的,而且背景會變暗,因此要Dialog實現彈出窗口的效果,就要解決任意位置顯示和背景不變暗的問題。
在Activity中寫如下代碼:


  1. public class MyActivity extends Activity {
  2. @Override
  3. public void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.main);
  6. //在指定位置顯示對話框
  7. showDialog(100, 100, 200, 200);
  8. }
  9. @Override
  10. protected Dialog onCreateDialog(int id, Bundle args) {
  11. return new AlertDialog.Builder(this)
  12. .setTitle("Hello Dialog!")
  13. .setMessage("Hello Dialog")
  14. .create();
  15. }
  16. @Override
  17. protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
  18. switch (id) {
  19. case 1: {
  20. //設置對話框的屬性
  21. Window window = dialog.getWindow();
  22. WindowManager.LayoutParams lp = window.getAttributes();
  23. lp.x = args.getInt("x");
  24. lp.y = args.getInt("y");
  25. lp.width = args.getInt("width");
  26. lp.height = args.getInt("height");
  27. lp.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND;
  28. dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.argb(0, 0, 0, 0)));
  29. }
  30. break;
  31. default:
  32. break;
  33. }
  34. super.onPrepareDialog(id, dialog, args);
  35. }
  36. private void showDialog(int x, int y, int width, int height) {
  37. Bundle bundle = new Bundle();
  38. bundle.putInt("x", x);
  39. bundle.putInt("y", y);
  40. bundle.putInt("width", width);
  41. bundle.putInt("height", height);
  42. showDialog(1, bundle);
  43. }
  44. }

在onPrepareDialog函數中,我們獲得了Dialog的Window,然後對位置和寬度進行了設置,並且通過Flag取消了背景變暗的效果,最後我們得到的結果如下圖:

其實,帶有Dialog主題的Activity也可以實現該效果,不過本人沒有實現背景不變暗,故不將代碼貼上了。


3、跨進程共享彈出窗口設計
我們能不能實現類似於QuickContact那樣的彈出窗口跨進程共享呢?當然是可以的。
可能我們會想到用一個透明的Activity,然後顯示一個PopupWindow來實現,但是PopupWindow我們之前說過,在Activity的onCreate函數中顯示時會有問題。
我們也可能還會想到一個透明的Activity加一個Dialog來顯示,但是我們也知道,Dialog並不是像PopupWindow那樣是輕量級的,僅僅一個Activity的顯示就夠耗費了,再顯示一個Dialog,那麼耗費就更大了。
我們可能還會想到用QuickContact的方式,創建一個Window,然後將該Window的根View貼到透明Activity上,然而,不幸的是,創建Window的函數是非公開的。
其實,我們不需要創建一個Window,也能將View貼到Activity之中。見如下代碼:


  1. public class PopupActivity extends Activity {
  2. private WindowManager mWindowManager;
  3. private View mAddedView;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.main);
  8. int x = getIntent().getIntExtra("x", 0);
  9. int y = getIntent().getIntExtra("y", 0);
  10. int width = getIntent().getIntExtra("width", 200);
  11. int height = getIntent().getIntExtra("height", 200);
  12. //該TextView要貼到透明的Activity上
  13. TextView textView = new TextView(this);
  14. textView.setBackgroundColor(Color.CYAN);
  15. textView.setWidth(width);
  16. textView.setHeight(height);
  17. mAddedView = textView;
  18. textView.setOnKeyListener(new OnKeyListener() {
  19. public boolean onKey(View v, int keyCode, KeyEvent event) {
  20. mWindowManager.removeView(mAddedView);
  21. finish();
  22. return false;
  23. }
  24. });
  25. WindowManager.LayoutParams params = new WindowManager.LayoutParams();
  26. params.x = x;
  27. params.y = y;
  28. params.width = width;
  29. params.height = height;
  30. params.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND;
  31. params.packageName = this.getPackageName();
  32. //貼到透明Activity上
  33. mWindowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
  34. mWindowManager.addView(textView, params);
  35. }
  36. }

該Activity要設置成透明,可以在AndroidManifest中加入android:theme = "@android:style/Theme.Translucent"。
調用的代碼如下:


  1. private void showPopupActivity(int x, int y, int width, int height) {
  2. Intent intent = new Intent();
  3. intent.setClass(this, DialogActivity.class);
  4. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
  5. | Intent.FLAG_ACTIVITY_CLEAR_TOP
  6. | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
  7. intent.putExtra("x", x);
  8. intent.putExtra("y", y);
  9. intent.putExtra("width", width);
  10. intent.putExtra("height", height);
  11. startActivity(intent);
  12. }

最終效果如下:

4、跨進程共享彈出窗口的優化
由於跨進程共享的彈出窗口是重啟的一個新的Activity,因此花銷是比較大的,為了提高效率,我們需要做一些工作。
首先,設置Activity的launchMode為singleTop,通過該設置,當該Activity已經在棧頂時,可以直接調用onNewIntent函數而不是重新創建。
其次,設置Activity的taskAffinity為其他值,如:android:taskAffinity = "com.winuxxan.dialogactivity",並且開啟Activity時,設置flag:


  1. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
  2. | Intent.FLAG_ACTIVITY_CLEAR_TOP
  3. | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

這樣,該Activity在一個單獨的Task中,並且隻要該Task堆棧中存在該Activity,那麼就不會重新創建,而是調用onNewIntent。
再次,當隱藏該窗口時,不是銷毀掉,而是moveTaskToBack,這樣再次顯示時就不是重新創建,而是將後台Task放到前台。
通過這些手段就可以顯著的提高該彈出窗口的效率。

最後更新:2017-04-02 22:16:32

  上一篇:go [筆記]Python對象基礎
  下一篇:go Ubuntu Linux 上交叉編譯FFmpeg Windows SDK