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

О том как написать Хеллоу Ворлд в интернете полным полно, так что я решил рассказать о более интересных вещах. Официальное приложение 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. Вот исходники самого главного класс, который реализует в себе всплывающий диалог. Для использования кастомного диалого просто наследуемся от него.

package az.mecid.popup.

import android.content.Context;

import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;

import android.widget.PopupWindow;

public class CustomPopupWindow {
	protected final View anchor;
	protected final PopupWindow window;
	private View root;
	private Drawable background = null;
	protected final WindowManager windowManager;
	
	public CustomPopupWindow(View anchor) {
		this.anchor = anchor;
		this.window = new PopupWindow(anchor.getContext());

		// Если происходит прикосновение за пределами диалогового окна,то окно закрывается
		window.setTouchInterceptor(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
					CustomPopupWindow.this.window.dismiss();
					
					return true;
				}
				
				return false;
			}
		});

		windowManager = (WindowManager) anchor.getContext().getSystemService(Context.WINDOW_SERVICE);
		
		onCreate();
	}

	protected void onCreate() {}


	protected void onShow() {}

	protected void preShow() {
		if (root == null) {
			throw new IllegalStateException("error");
		}
		
		onShow();

		if (background == null) {
			window.setBackgroundDrawable(new BitmapDrawable());
		} else {
			window.setBackgroundDrawable(background);
		}
		
		window.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
		window.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
		window.setTouchable(true);
		window.setFocusable(true);
		window.setOutsideTouchable(true);

		window.setContentView(root);
	}

	public void setBackgroundDrawable(Drawable background) {
		this.background = background;
	}

	public void setContentView(View root) {
		this.root = root;
		
		window.setContentView(root);
	}

	public void setContentView(int layoutResID) {
		LayoutInflater inflator =
				(LayoutInflater) anchor.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		
		setContentView(inflator.inflate(layoutResID, null));
	}


	public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
		window.setOnDismissListener(listener);
	}


	public void showDropDown() {
		showDropDown(0, 0);
	}

	public void showDropDown(int xOffset, int yOffset) {
		preShow();

		window.setAnimationStyle(R.style.Animations_PopDownMenu);

		window.showAsDropDown(anchor, xOffset, yOffset);
	}

	public void showLikeQuickAction() {
		showLikeQuickAction(0, 0);
	}

	public void showLikeQuickAction(int xOffset, int yOffset) {
		preShow();

		window.setAnimationStyle(R.style.Animations_PopUpMenu_Center);

		int[] location = new int[2];
		anchor.getLocationOnScreen(location);

		Rect anchorRect =
				new Rect(location[0], location[1], location[0] + anchor.getWidth(), location[1]
					+ anchor.getHeight());

		root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
		root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
		
		int rootWidth 		= root.getMeasuredWidth();
		int rootHeight 		= root.getMeasuredHeight();

		int screenWidth 	= windowManager.getDefaultDisplay().getWidth();

		int xPos 			= ((screenWidth - rootWidth) / 2) + xOffset;
		int yPos	 		= anchorRect.top - rootHeight + yOffset;


		if (rootHeight > anchorRect.top) {
			yPos = anchorRect.bottom + yOffset;
			
			window.setAnimationStyle(R.style.Animations_PopDownMenu_Center);
		}

		window.showAtLocation(anchor, Gravity.NO_GRAVITY, xPos, yPos);
	}
	
	public void dismiss() {
		window.dismiss();
	}
}

Теперь нам нужно написать класс, который будет являтся конкретным Action.

package az.mecid.popups;

import android.graphics.drawable.Drawable;
import android.view.View.OnClickListener;

public class ActionItem {
	private Drawable icon;
	private String title;
	private OnClickListener listener;
	
	public ActionItem() {}
	
	public ActionItem(Drawable icon) {
		this.icon = icon;
	}
	
	public void setTitle(String title) {
		this.title = title;
	}
	
	public String getTitle() {
		return this.title;
	}
	
	public void setIcon(Drawable icon) {
		this.icon = icon;
	}
	
	public Drawable getIcon() {
		return this.icon;
	}
	

	public void setOnClickListener(OnClickListener listener) {
		this.listener = listener;
	}
	
	public OnClickListener getListener() {
		return this.listener;
	}
}

Теперь перейдем к самому интересному созданию самого диалогового окна.
Нам надо будет создать класс, который наследуется от CustomPopupWindow
package az.mecid.popups;

import android.content.Context;

import android.graphics.Rect;
import android.graphics.drawable.Drawable;

import android.widget.ImageView;
import android.widget.TextView;
import android.widget.LinearLayout;
import android.widget.ScrollView;

import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup;

import java.util.ArrayList;

public class QuickAction extends CustomPopupWindow {
	private final View root;
	private final ImageView mArrowUp;
	private final ImageView mArrowDown;
	private final LayoutInflater inflater;
	private final Context context;
	
	protected static final int ANIM_GROW_FROM_LEFT = 1;
	protected static final int ANIM_GROW_FROM_RIGHT = 2;
	protected static final int ANIM_GROW_FROM_CENTER = 3;
	protected static final int ANIM_REFLECT = 4;
	protected static final int ANIM_AUTO = 5;
	
	private int animStyle;
	private ViewGroup mTrack;
	private ScrollView scroller;
	private ArrayList<ActionItem> actionList;
	
	public QuickAction(View anchor) {
		super(anchor);
		
		actionList	= new ArrayList<ActionItem>();
		context		= anchor.getContext();
		inflater 	= (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		
		root		= (ViewGroup) inflater.inflate(R.layout.popup, null);
		
		mArrowDown 	= (ImageView) root.findViewById(R.id.arrow_down);
		mArrowUp 	= (ImageView) root.findViewById(R.id.arrow_up);
		
		setContentView(root);
	    
		mTrack 			= (ViewGroup) root.findViewById(R.id.tracks);
		scroller		= (ScrollView) root.findViewById(R.id.scroller);
		animStyle		= ANIM_AUTO;
	}

	public void setAnimStyle(int animStyle) {
		this.animStyle = animStyle;
	}

	public void addActionItem(ActionItem action) {
		actionList.add(action); 
	}
	
	public void show () {
		preShow();
		
		int xPos, yPos;
		
		int[] location 		= new int[2];
	
		anchor.getLocationOnScreen(location);

		Rect anchorRect 	= new Rect(location[0], location[1], location[0] + anchor.getWidth(), location[1] 
		                	+ anchor.getHeight());

		createActionList();
		
		root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
		root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
	
		int rootHeight 		= root.getMeasuredHeight();
		int rootWidth		= root.getMeasuredWidth();
		
		int screenWidth 	= windowManager.getDefaultDisplay().getWidth();
		int screenHeight	= windowManager.getDefaultDisplay().getHeight();
		
	if ((anchorRect.left + rootWidth) > screenWidth) {
			xPos = anchorRect.left - (rootWidth-anchor.getWidth());
		} else {
			if (anchor.getWidth() > rootWidth) {
				xPos = anchorRect.centerX() - (rootWidth/2);
			} else {
				xPos = anchorRect.left;
			}
		}
		
		int dyTop			= anchorRect.top;
		int dyBottom		= screenHeight - anchorRect.bottom;

		boolean onTop		= (dyTop > dyBottom) ? true : false;

		if (onTop) {
			if (rootHeight > dyTop) {
				yPos 			= 15;
				LayoutParams l 	= scroller.getLayoutParams();
				l.height		= dyTop - anchor.getHeight();
			} else {
				yPos = anchorRect.top - rootHeight;
			}
		} else {
			yPos = anchorRect.bottom;
			
			if (rootHeight > dyBottom) { 
				LayoutParams l 	= scroller.getLayoutParams();
				l.height		= dyBottom;
			}
		}
		
		showArrow(((onTop) ? R.id.arrow_down : R.id.arrow_up), anchorRect.centerX()-xPos);
		
		setAnimationStyle(screenWidth, anchorRect.centerX(), onTop);
		
		window.showAtLocation(anchor, Gravity.NO_GRAVITY, xPos, yPos);
	}
	
	private void setAnimationStyle(int screenWidth, int requestedX, boolean onTop) {
		int arrowPos = requestedX - mArrowUp.getMeasuredWidth()/2;

		switch (animStyle) {
		case ANIM_GROW_FROM_LEFT:
			window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left : R.style.Animations_PopDownMenu_Left);
			break;
					
		case ANIM_GROW_FROM_RIGHT:
			window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right : R.style.Animations_PopDownMenu_Right);
			break;
					
		case ANIM_GROW_FROM_CENTER:
			window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center : R.style.Animations_PopDownMenu_Center);
		break;
			
		case ANIM_REFLECT:
			window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Reflect : R.style.Animations_PopDownMenu_Reflect);
		break;
		
		case ANIM_AUTO:
			if (arrowPos <= screenWidth/4) {
				window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left : R.style.Animations_PopDownMenu_Left);
			} else if (arrowPos > screenWidth/4 && arrowPos < 3 * (screenWidth/4)) {
				window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center : R.style.Animations_PopDownMenu_Center);
			} else {
				window.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right : R.style.Animations_PopDownMenu_Right);
			}
					
			break;
		}
	}
	
	private void createActionList() {
		View view;
		String title;
		Drawable icon;
		OnClickListener listener;
	
		for (int i = 0; i < actionList.size(); i++) {
			title 		= actionList.get(i).getTitle();
			icon 		= actionList.get(i).getIcon();
			listener	= actionList.get(i).getListener();
	
			view 		= getActionItem(title, icon, listener);
		
			view.setFocusable(true);
			view.setClickable(true);
			 
			mTrack.addView(view);
		}
	}
	
	private View getActionItem(String title, Drawable icon, OnClickListener listener) {
		LinearLayout container	= (LinearLayout) inflater.inflate(R.layout.action_item, null);
		
		ImageView img			= (ImageView) container.findViewById(R.id.icon);
		TextView text			= (TextView) container.findViewById(R.id.title);
		
		if (icon != null) {
			img.setImageDrawable(icon);
		}
		
		if (title != null) {			
			text.setText(title);
		}
		
		if (listener != null) {
			container.setOnClickListener(listener);
		}

		return container;
	}
	

	private void showArrow(int whichArrow, int requestedX) {
        final View showArrow = (whichArrow == R.id.arrow_up) ? mArrowUp : mArrowDown;
        final View hideArrow = (whichArrow == R.id.arrow_up) ? mArrowDown : mArrowUp;

        final int arrowWidth = mArrowUp.getMeasuredWidth();

        showArrow.setVisibility(View.VISIBLE);
        
        ViewGroup.MarginLayoutParams param = (ViewGroup.MarginLayoutParams)showArrow.getLayoutParams();
       
        param.leftMargin = requestedX - arrowWidth / 2;
        
        hideArrow.setVisibility(View.INVISIBLE);
    }
}


Пусть вас не пугает код, использовать его очень просто, теперь попробуем использовать весь этот код.
Не буду приводить весь код, тут все предельно просто.
final ActionItem first = new ActionItem();
		
		first.setTitle("Dashboard");
		first.setIcon(getResources().getDrawable(R.drawable.dashboard));
		first.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Toast.makeText(TestQuickAction.this, "Dashboard" , Toast.LENGTH_SHORT).show();
			}
		});
		
		
		final ActionItem second = new ActionItem();
		
		second.setTitle("Users & Groups");
		second.setIcon(getResources().getDrawable(R.drawable.kontak));
		second.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Toast.makeText(TestQuickAction.this, "Users & Groups", Toast.LENGTH_SHORT).show();
			}
		});
		
		Button btn1 = (Button) this.findViewById(R.id.btn1);
		btn1.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				QuickAction qa = new QuickAction(v);
				
				qa.addActionItem(first);
				qa.addActionItem(second);
				
				qa.show();
			}
		});



P.S. используйте в своих приложениях.
P.S.S Оригинал статьи лежит в моем блоге
  • +6
  • 04 сентября 2010, 23:01
  • Mecid

Комментарии (7)

RSS свернуть / развернуть
+
+1
такие вещи здесь кому то интересны?
avatar

Mecid

  • 05 сентября 2010, 00:14
+
0
Мне интересно. Пеши исчо :)
avatar

baxi

  • 07 сентября 2010, 22:42
+
+1
Респект тебе, надеюсь тут найдутся те кому это будет интересно, писать под Андро будет все популярнее с каждым годом:)
avatar

Cluster

  • 05 сентября 2010, 01:39
+
+1
Респект тебе за пост! Надеюс будешь и дальше писать про разработку на андроид. Можешь начать с более простых вещей)
avatar

Adil

  • 05 сентября 2010, 02:15
+
0
спасибо)буду писать еще)было бы отлично если подкините темку)
avatar

Mecid

  • 05 сентября 2010, 02:45
+
0
статья полезная… но жду активности наших в Аппсторе и Cydia
avatar

Ramin

  • 05 сентября 2010, 22:19
+
0
немного не понял, при чем тут Appstore и Cydia?
avatar

Mecid

  • 07 сентября 2010, 23:35

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.