Создание QuickAction диалогов в Android

  • Tutorial
О том как написать Хеллоу Ворлд в интернете полным полно, так что я решил рассказать о более интересных вещах. Официальное приложение Twitter для Android использует паттерны и возможности графического интерфейса появившиеся в последних версиях sdk, такие как Dashboard, Search Bar, QuickAction и Action Bar. Диалог QuickAction является одной из самых интересных новинок, он отображает контекстное действия для данного элемента ListView. Этот диалог используется также в приложении контактов, начиная с версии 2.0



Диалог QuickAction не входит в стандартный sdk, поэтому мы попытаем создать его самим. Изначально у меня не было никаких идей, оставалось только скачать исходники приложения Contacts и посмотреть, как это делается, благо android это open source. Копаясь в исходниках я заметил, что там используются приватные API вызовы(com.android.internal.policy.PolicyManager), которые недоступны в стандартном sdk.Немного поразмыслив я нашел выход, построенный на основе исходников Сontacts. Вот исходники самого главного класс, который реализует в себе всплывающий диалог. Для использования кастомного диалого просто наследуемся от него.
  1. package az.mecid.popup.
  2.  
  3. import android.content.Context;
  4.  
  5. import android.graphics.Rect;
  6. import android.graphics.drawable.BitmapDrawable;
  7. import android.graphics.drawable.Drawable;
  8.  
  9. import android.view.Gravity;
  10. import android.view.LayoutInflater;
  11. import android.view.MotionEvent;
  12. import android.view.View;
  13. import android.view.View.OnTouchListener;
  14. import android.view.ViewGroup.LayoutParams;
  15. import android.view.WindowManager;
  16.  
  17. import android.widget.PopupWindow;
  18.  
  19. public class CustomPopupWindow {
  20.   protected final View anchor;
  21.   protected final PopupWindow window;
  22.   private View root;
  23.   private Drawable background = null;
  24.   protected final WindowManager windowManager;
  25.   
  26.   public CustomPopupWindow(View anchor) {
  27.     this.anchor = anchor;
  28.     this.window = new PopupWindow(anchor.getContext());
  29.  
  30.     // Если происходит прикосновение за пределами диалогового окна,то окно закрывается
  31.     window.setTouchInterceptor(new OnTouchListener() {
  32.       @Override
  33.       public boolean onTouch(View v, MotionEvent event) {
  34.         if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
  35.           CustomPopupWindow.this.window.dismiss();
  36.           
  37.           return true;
  38.         }
  39.         
  40.         return false;
  41.       }
  42.     });
  43.  
  44.     windowManager = (WindowManager) anchor.getContext().getSystemService(Context.WINDOW_SERVICE);
  45.     
  46.     onCreate();
  47.   }
  48.  
  49.   protected void onCreate() {}
  50.  
  51.  
  52.   protected void onShow() {}
  53.  
  54.   protected void preShow() {
  55.     if (root == null) {
  56.       throw new IllegalStateException("error");
  57.     }
  58.     
  59.     onShow();
  60.  
  61.     if (background == null) {
  62.       window.setBackgroundDrawable(new BitmapDrawable());
  63.     } else {
  64.       window.setBackgroundDrawable(background);
  65.     }
  66.     
  67.     window.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
  68.     window.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
  69.     window.setTouchable(true);
  70.     window.setFocusable(true);
  71.     window.setOutsideTouchable(true);
  72.  
  73.     window.setContentView(root);
  74.   }
  75.  
  76.   public void setBackgroundDrawable(Drawable background) {
  77.     this.background = background;
  78.   }
  79.  
  80.   public void setContentView(View root) {
  81.     this.root = root;
  82.     
  83.     window.setContentView(root);
  84.   }
  85.  
  86.   public void setContentView(int layoutResID) {
  87.     LayoutInflater inflator =
  88.         (LayoutInflater) anchor.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  89.     
  90.     setContentView(inflator.inflate(layoutResID, null));
  91.   }
  92.  
  93.  
  94.   public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
  95.     window.setOnDismissListener(listener);
  96.   }
  97.  
  98.  
  99.   public void showDropDown() {
  100.     showDropDown(0, 0);
  101.   }
  102.  
  103.   public void showDropDown(int xOffset, int yOffset) {
  104.     preShow();
  105.  
  106.     window.setAnimationStyle(R.style.Animations_PopDownMenu);
  107.  
  108.     window.showAsDropDown(anchor, xOffset, yOffset);
  109.   }
  110.  
  111.   public void showLikeQuickAction() {
  112.     showLikeQuickAction(0, 0);
  113.   }
  114.  
  115.   public void showLikeQuickAction(int xOffset, int yOffset) {
  116.     preShow();
  117.  
  118.     window.setAnimationStyle(R.style.Animations_PopUpMenu_Center);
  119.  
  120.     int[] location = new int[2];
  121.     anchor.getLocationOnScreen(location);
  122.  
  123.     Rect anchorRect =
  124.         new Rect(location[0], location[1], location[0] + anchor.getWidth(), location[1]
  125.           + anchor.getHeight());
  126.  
  127.     root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
  128.     root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
  129.     
  130.     int rootWidth     = root.getMeasuredWidth();
  131.     int rootHeight     = root.getMeasuredHeight();
  132.  
  133.     int screenWidth   = windowManager.getDefaultDisplay().getWidth();
  134.  
  135.     int xPos       = ((screenWidth - rootWidth) / 2) + xOffset;
  136.     int yPos       = anchorRect.top - rootHeight + yOffset;
  137.  
  138.  
  139.     if (rootHeight > anchorRect.top) {
  140.       yPos = anchorRect.bottom + yOffset;
  141.       
  142.       window.setAnimationStyle(R.style.Animations_PopDownMenu_Center);
  143.     }
  144.  
  145.     window.showAtLocation(anchor, Gravity.NO_GRAVITY, xPos, yPos);
  146.   }
  147.   
  148.   public void dismiss() {
  149.     window.dismiss();
  150.   }
  151. }
* This source code was highlighted with Source Code Highlighter
.

Теперь нам нужно написать класс, который будет являтся конкретным Action.
  1. package az.mecid.popups;
  2.  
  3. import android.graphics.drawable.Drawable;
  4. import android.view.View.OnClickListener;
  5.  
  6. public class ActionItem {
  7.   private Drawable icon;
  8.   private String title;
  9.   private OnClickListener listener;
  10.   
  11.   public ActionItem() {}
  12.   
  13.   public ActionItem(Drawable icon) {
  14.     this.icon = icon;
  15.   }
  16.   
  17.   public void setTitle(String title) {
  18.     this.title = title;
  19.   }
  20.   
  21.   public String getTitle() {
  22.     return this.title;
  23.   }
  24.   
  25.   public void setIcon(Drawable icon) {
  26.     this.icon = icon;
  27.   }
  28.   
  29.   public Drawable getIcon() {
  30.     return this.icon;
  31.   }
  32.   
  33.  
  34.   public void setOnClickListener(OnClickListener listener) {
  35.     this.listener = listener;
  36.   }
  37.   
  38.   public OnClickListener getListener() {
  39.     return this.listener;
  40.   }
  41. }
* This source code was highlighted with Source Code Highlighter
.


Теперь перейдем к самому интересному созданию самого диалогового окна.
Нам надо будет создать класс, который наследуется от CustomPopupWindow
  1. package az.mecid.popups;
  2.  
  3. import android.content.Context;
  4.  
  5. import android.graphics.Rect;
  6. import android.graphics.drawable.Drawable;
  7.  
  8. import android.widget.ImageView;
  9. import android.widget.TextView;
  10. import android.widget.LinearLayout;
  11. import android.widget.ScrollView;
  12.  
  13. import android.view.Gravity;
  14. import android.view.LayoutInflater;
  15. import android.view.View;
  16. import android.view.View.OnClickListener;
  17. import android.view.ViewGroup.LayoutParams;
  18. import android.view.ViewGroup;
  19.  
  20. import java.util.ArrayList;
  21.  
  22. public class QuickAction extends CustomPopupWindow {
  23.   private final View root;
  24.   private final ImageView mArrowUp;
  25.   private final ImageView mArrowDown;
  26.   private final LayoutInflater inflater;
  27.   private final Context context;
  28.   
  29.   protected static final int ANIM_GROW_FROM_LEFT = 1;
  30.   protected static final int ANIM_GROW_FROM_RIGHT = 2;
  31.   protected static final int ANIM_GROW_FROM_CENTER = 3;
  32.   protected static final int ANIM_REFLECT = 4;
  33.   protected static final int ANIM_AUTO = 5;
  34.   
  35.   private int animStyle;
  36.   private ViewGroup mTrack;
  37.   private ScrollView scroller;
  38.   private ArrayList<ActionItem> actionList;
  39.   
  40.   public QuickAction(View anchor) {
  41.     super(anchor);
  42.     
  43.     actionList  = new ArrayList<ActionItem>();
  44.     context    = anchor.getContext();
  45.     inflater   = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  46.     
  47.     root    = (ViewGroup) inflater.inflate(R.layout.popup, null);
  48.     
  49.     mArrowDown   = (ImageView) root.findViewById(R.id.arrow_down);
  50.     mArrowUp   = (ImageView) root.findViewById(R.id.arrow_up);
  51.     
  52.     setContentView(root);
  53.     
  54.     mTrack       = (ViewGroup) root.findViewById(R.id.tracks);
  55.     scroller    = (ScrollView) root.findViewById(R.id.scroller);
  56.     animStyle    = ANIM_AUTO;
  57.   }
  58.  
  59.   public void setAnimStyle(int animStyle) {
  60.     this.animStyle = animStyle;
  61.   }
  62.  
  63.   public void addActionItem(ActionItem action) {
  64.     actionList.add(action);
  65.   }
  66.   
  67.   public void show () {
  68.     preShow();
  69.     
  70.     int xPos, yPos;
  71.     
  72.     int[] location     = new int[2];
  73.   
  74.     anchor.getLocationOnScreen(location);
  75.  
  76.     Rect anchorRect   = new Rect(location[0], location[1], location[0] + anchor.getWidth(), location[1]
  77.               + anchor.getHeight());
  78.  
  79.     createActionList();
  80.     
  81.     root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
  82.     root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
  83.   
  84.     int rootHeight     = root.getMeasuredHeight();
  85.     int rootWidth    = root.getMeasuredWidth();
  86.     
  87.     int screenWidth   = windowManager.getDefaultDisplay().getWidth();
  88.     int screenHeight  = windowManager.getDefaultDisplay().getHeight();
  89.     
  90.   if ((anchorRect.left + rootWidth) > screenWidth) {
  91.       xPos = anchorRect.left - (rootWidth-anchor.getWidth());
  92.     } else {
  93.       if (anchor.getWidth() > rootWidth) {
  94.         xPos = anchorRect.centerX() - (rootWidth/2);
  95.       } else {
  96.         xPos = anchorRect.left;
  97.       }
  98.     }
  99.     
  100.     int dyTop      = anchorRect.top;
  101.     int dyBottom    = screenHeight - anchorRect.bottom;
  102.  
  103.     boolean onTop    = (dyTop > dyBottom) ? true : false;
  104.  
  105.     if (onTop) {
  106.       if (rootHeight > dyTop) {
  107.         yPos       = 15;
  108.         LayoutParams l   = scroller.getLayoutParams();
  109.         l.height    = dyTop - anchor.getHeight();
  110.       } else {
  111.         yPos = anchorRect.top - rootHeight;
  112.       }
  113.     } else {
  114.       yPos = anchorRect.bottom;
  115.       
  116.       if (rootHeight > dyBottom) {
  117.         LayoutParams l   = scroller.getLayoutParams();
  118.         l.height    = dyBottom;
  119.       }
  120.     }
  121.     
  122.     showArrow(((onTop) ? R.id.arrow_down : R.id.arrow_up), anchorRect.centerX()-xPos);
  123.     
  124.     setAnimationStyle(screenWidth, anchorRect.centerX(), onTop);
  125.     
  126.     window.showAtLocation(anchor, Gravity.NO_GRAVITY, xPos, yPos);
  127.   }
  128.   
  129.   private void setAnimationStyle(int screenWidth, int requestedX, boolean onTop) {
  130.     int arrowPos = requestedX - mArrowUp.getMeasuredWidth()/2;
  131.  
  132.     switch (animStyle) {
  133.     case ANIM_GROW_FROM_LEFT:
  134.       window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left : R.style.Animations_PopDownMenu_Left);
  135.       break;
  136.           
  137.     case ANIM_GROW_FROM_RIGHT:
  138.       window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right : R.style.Animations_PopDownMenu_Right);
  139.       break;
  140.           
  141.     case ANIM_GROW_FROM_CENTER:
  142.       window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center : R.style.Animations_PopDownMenu_Center);
  143.     break;
  144.       
  145.     case ANIM_REFLECT:
  146.       window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Reflect : R.style.Animations_PopDownMenu_Reflect);
  147.     break;
  148.     
  149.     case ANIM_AUTO:
  150.       if (arrowPos <= screenWidth/4) {
  151.         window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left : R.style.Animations_PopDownMenu_Left);
  152.       } else if (arrowPos > screenWidth/4 && arrowPos < 3 * (screenWidth/4)) {
  153.         window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center : R.style.Animations_PopDownMenu_Center);
  154.       } else {
  155.         window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right : R.style.Animations_PopDownMenu_Right);
  156.       }
  157.           
  158.       break;
  159.     }
  160.   }
  161.   
  162.   private void createActionList() {
  163.     View view;
  164.     String title;
  165.     Drawable icon;
  166.     OnClickListener listener;
  167.   
  168.     for (int i = 0; i < actionList.size(); i++) {
  169.       title     = actionList.get(i).getTitle();
  170.       icon     = actionList.get(i).getIcon();
  171.       listener  = actionList.get(i).getListener();
  172.   
  173.       view     = getActionItem(title, icon, listener);
  174.     
  175.       view.setFocusable(true);
  176.       view.setClickable(true);
  177.       
  178.       mTrack.addView(view);
  179.     }
  180.   }
  181.   
  182.   private View getActionItem(String title, Drawable icon, OnClickListener listener) {
  183.     LinearLayout container  = (LinearLayout) inflater.inflate(R.layout.action_item, null);
  184.     
  185.     ImageView img      = (ImageView) container.findViewById(R.id.icon);
  186.     TextView text      = (TextView) container.findViewById(R.id.title);
  187.     
  188.     if (icon != null) {
  189.       img.setImageDrawable(icon);
  190.     }
  191.     
  192.     if (title != null) {      
  193.       text.setText(title);
  194.     }
  195.     
  196.     if (listener != null) {
  197.       container.setOnClickListener(listener);
  198.     }
  199.  
  200.     return container;
  201.   }
  202.   
  203.  
  204.   private void showArrow(int whichArrow, int requested {
  205.     final View showArrow = (whichArrow == R.id.arrow_up) ? mArrowUp : mArrowDown;
  206.     final View hideArrow = (whichArrow == R.id.arrow_up) ? mArrowDown : mArrowUp;
  207.  
  208.     final int arrowWidth = mArrowUp.getMeasuredWidth();
  209.  
  210.     showArrow.setVisibility(View.VISIBLE);
  211.     
  212.     ViewGroup.MarginLayoutParams param = (ViewGroup.MarginLayoutParams)showArrow.getLayoutParams();
  213.    
  214.     param.leftMargin = requestedX - arrowWidth / 2;
  215.     
  216.     hideArrow.setVisibility(View.INVISIBLE);
  217.   }
  218. }
* This source code was highlighted with Source Code Highlighter
.


Пусть вас не пугает код, использовать его очень просто, теперь попробуем использовать весь этот код.
Не буду приводить весь код, тут все предельно просто.
  1. final ActionItem first = new ActionItem();
  2.     
  3.     first.setTitle("Dashboard");
  4.     first.setIcon(getResources().getDrawable(R.drawable.dashboard));
  5.     first.setOnClickListener(new OnClickListener() {
  6.       @Override
  7.       public void onClick(View v) {
  8.         Toast.makeText(TestQuickAction.this, "Dashboard" , Toast.LENGTH_SHORT).show();
  9.       }
  10.     });
  11.     
  12.     
  13.     final ActionItem second = new ActionItem();
  14.     
  15.     second.setTitle("Users & Groups");
  16.     second.setIcon(getResources().getDrawable(R.drawable.kontak));
  17.     second.setOnClickListener(new OnClickListener() {
  18.       @Override
  19.       public void onClick(View v) {
  20.         Toast.makeText(TestQuickAction.this, "Users & Groups", Toast.LENGTH_SHORT).show();
  21.       }
  22.     });
  23.     
  24.     Button btn1 = (Button) this.findViewById(R.id.btn1);
  25.     btn1.setOnClickListener(new View.OnClickListener() {
  26.       @Override
  27.       public void onClick(View v) {
  28.         QuickAction qa = new QuickAction(v);
  29.         
  30.         qa.addActionItem(first);
  31.         qa.addActionItem(second);
  32.         
  33.         qa.show();
  34.       }
  35.     });
* This source code was highlighted with Source Code Highlighter
.



P.S. используйте в своих приложениях.
P.S.S Оригинал статьи лежит в моем блоге
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 13

    0
    Подсветите синтаксис пожалуйста.
    Автору и вам благодарность большая, хороших мануалов немного, тем более с примерами.
      0
      Подсветил чем нашел, щас поищу подсветку с поддержкой Java.
        +1
        На Хабре же теперь есть встроенная подсветка синтаксиса: <source lang="java"></source>. Пример:
        int a = 1;
          0
          Обновите пожалуйста подстветку синтаксиса. Когда-то давно мне эта статься помогла, думаю еще кому нибудь поможет.
        0
        Спасибо зa статью, возможно, использую в своем приложении :) А для работы вашего класса нужен Android SDK 2.0 или выше, нa 1.5 и 1.6 работать не будет (HTC Hero, Magic, etc.)?
          0
          HTC Hero получил 2.1, да работает на прошивка начиная с 2.0
            0
            Почему на 1.6 не будет работать?
            А за статью спасибо.
              0
              сам я не пробовал, но работать вроде не должно.Если потестите отпишитесь
          0
          Спасибо, почитаемс :)
            0
            Спасибо большое! Пишите еще!
              +1
              Обожаю такие статьи: 200 строк кода и две строки пояснений :)
                0
                код предельно простой, а его использование еще проще, для человека писавшего под android
                  +1
                  Какой бы код простым не был, человек — не парсер. Ему сложно воспринимать такие «стены кода».

              Only users with full accounts can post comments. Log in, please.