I'm trying to create a select menu in Android instead of using Spinner, but i'm facing some problems with the layout. How can I create something like the image below using ListPopUpWindow?
Thanks
I really like these menus floating over selected items, so I created my own DropDown class which implements your case. It does pretty much what #uguboz wrote.
I'm using PopupWindow with a custom layout with a RecyclerView. Then I handle onClick, display that window and use overriden PopupWindow.update() to calculate correct window position.
The most interesting part would be this code:
public class DropDownMenu extends PopupWindow {
public boolean show(View anchor) {
mAnchorView = anchor;
super.showAtLocation(anchor, Gravity.START | Gravity.TOP, 0, 0);
update();
return true;
}
public void update() {
final Resources res = getContentView().getContext().getResources();
int margin = (int) res.getDimension(R.dimen.carbon_margin);
int itemHeight = (int) res.getDimension(R.dimen.carbon_listItemHeight);
int marginHalf = (int) res.getDimension(R.dimen.carbon_paddingHalf);
ArrayAdapter adapter = getAdapter();
Rect windowRect = new Rect();
mAnchorView.getWindowVisibleDisplayFrame(windowRect);
int hWindow = windowRect.bottom - windowRect.top;
int wWindow = windowRect.right - windowRect.left;
int[] location = new int[2];
mAnchorView.getLocationInWindow(location);
if (mode == DropDown.Mode.Over) {
int maxHeightAbove = location[1] - windowRect.top - marginHalf * 2;
int maxItemsAbove = maxHeightAbove / itemHeight;
int maxHeightBelow = hWindow - location[1] - marginHalf * 2;
int maxItemsBelow = maxHeightBelow / itemHeight;
int itemsBelow = Math.min(adapter.getItemCount() - selectedItem, maxItemsBelow);
int itemsAbove = Math.min(selectedItem, maxItemsAbove);
int popupX = location[0] - margin - marginHalf;
int popupY = location[1] - marginHalf * 2 - itemsAbove * itemHeight - (itemHeight - (mAnchorView.getHeight() - mAnchorView.getPaddingTop() -
mAnchorView.getPaddingBottom())) / 2 + mAnchorView.getPaddingTop();
int popupWidth = mAnchorView.getWidth() + margin * 2 + marginHalf * 2 - mAnchorView.getPaddingLeft() - mAnchorView.getPaddingRight();
int popupHeight = marginHalf * 4 + Math.max(1, itemsAbove + itemsBelow) * itemHeight;
popupWidth = Math.min(popupWidth, wWindow - marginHalf * 2);
popupX = Math.max(popupX, 0);
popupX = Math.min(popupX, wWindow - popupWidth);
LinearLayoutManager manager = (LinearLayoutManager) recycler.getLayoutManager();
manager.scrollToPositionWithOffset(selectedItem - itemsAbove, 0);
update(popupX, popupY, popupWidth, popupHeight);
} else {
// not interesting
}
super.update();
}
}
The code is way too long to paste it here with all details, so I'll serve you a link to the class: DropDownMenu. Use it as you wish. I hope you'll find the code useful.
And I've made a sample for that image from the guidelines. It can be found in the sample app under Guidelines -> Menus/Behavior
Related
I'm trying to scale view from start rectangle (e.g. defined by another view) to it's final position.
I tried to use the following code to setup animations which looks straight forward:
float scaleX = 0f;
float scaleY = 0f;
Rect startRect = new Rect(10, 10, 100, 100); // taken from real view position with getLocationOnScreen
final Collection<Animator> animators = new ArrayList<>();
if (animatedView.getMeasuredHeight() != 0) {
scaleX = (float)startRect.width() / animatedView.getMeasuredWidth();
}
if (animatedView.getMeasuredHeight() != 0) {
scaleY = (float)startRect.height() / animatedView.getMeasuredHeight();
}
animatedView.getLocationInWindow(location);
animatedView.setPivotX(startRect.left);
animatedView.setPivotY(startRect.top);
animatedView.setScaleX(scaleX);
animatedView.setScaleY(scaleY);
animators.add(ObjectAnimator.ofFloat(animatedView, View.SCALE_X, 1.0f).setDuration(1000));
animators.add(ObjectAnimator.ofFloat(animatedView, View.SCALE_Y, 1.0f).setDuration(1000));
The animatedView is child of RelativeLayout (layout parameters set to below some title view of layout) and measured width and height and location are valid values at the moment of animation setup.
Depending on startRect I observe different animations - sometimes animated view get displayed below or above startRect.
Seems RectEvaluator is one of possible solutions, but it's available only from API 18.
What is the proper way to animate view from start rectangle position to final (not modified one)?
As per comments on the question, it's possible to copy RectEvaluator code from Android source, and then apply the following logic:
RectViewAnimator mRectAnimator;
/**
* Creates animator which can be played. From some start position
* to final (real position).
* From final position to start position can be achieved using reverse interpolation.
*/
private Collection<Animator> createMoveAnimators(View targetView, Rect startRect) {
final Collection<Animator> animators = new ArrayList<>();
final int[] location = new int[2];
targetView.getLocationOnScreen(location);
final Rect finalRect = new Rect(location[0], location[1],
location[0] + targetView.getMeasuredWidth(),
location[1] + targetView.getMeasuredHeight());
// Must keep this reference during animations, since Animator keeps only WeakReference to it's targets.
mRectAnimator = appendRectEvaluatorAnimation(animators, targetView, 500, startRect, finalRect);
return animators;
}
private RectViewAnimator appendRectEvaluatorAnimation(final Collection<Animator> animators, final View view, final int duration,
final Rect startRect, final Rect finalRect) {
final float scaleX = (float) startRect.width() / finalRect.width();
final float scaleY = (float) startRect.height() / finalRect.height();
view.setTranslationY(startRect.top - (finalRect.top + (1 - scaleY) * finalRect.height() / 2));
view.setTranslationX(startRect.left - (finalRect.left + (1 - scaleX) * finalRect.width() / 2));
view.setScaleX(scaleX);
view.setScaleY(scaleY);
final RectViewAnimator rectViewAnimator = new RectViewAnimator(view, finalRect);
final Animator animator = ObjectAnimator.ofObject(rectViewAnimator, RectViewAnimator.RECT,
new RectEvaluator(), startRect, finalRect);
animators.add(animator);
return rectViewAnimator;
}
private static class RectViewAnimator {
static final String RECT = "rect";
private final View target;
private final Rect finalRect;
RectViewAnimator(final View target, final Rect finalRect) {
this.target = target;
this.finalRect = finalRect;
}
#Keep
public void setRect(final Rect r) {
final float scaleX = (float)r.width() / finalRect.width();
final float scaleY = (float)r.height() / finalRect.height();
target.setScaleX(scaleX);
target.setScaleY(scaleY);
target.setTranslationX(r.left - (finalRect.left + (1 - scaleX) * finalRect.width() / 2));
target.setTranslationY(r.top - (finalRect.top + (1 - scaleY) * finalRect.height() / 2));
}
}
I have used the following code, but it is not working:
int startX = driver.findElement(By.xpath("//*[#class='android.widget.FrameLayout' and #index='1']")).getLocation.getX();
int startY = driver.findElement(By.xpath("//*[#class='android.widget.FrameLayout' and #index='1']")).getLocation.getY();
and the error I get is:
getLocation cannot be resolved or is not a field
Firstly you should try getting location as getLocation()instead of: .getLocation.getY().
Secondly you can implement swipe/scroll in all directions using:
TouchAction action = new TouchAction(driver);
int startY = element1.getLocation().getY() + (element1.getSize().getHeight() / 2);
int startX = element1.getLocation().getX() + (element1.getSize().getWidth() / 2);
int endX = element2.getLocation().getX() + (element2.getSize().getWidth() / 2);
int endY = element2.getLocation().getY() + (element2.getSize().getHeight() / 2);
action.press(startX, startY).waitAction(2000).moveTo(endX, endY).release().perform();
public void horizontalScroll()
{
size=driver.manage().window().getSize();
int x_start=(int)(size.width*0.60);
int x_end=(int)(size.width*0.30);
int y=130;
driver.swipe(x_start,y,x_end,y,4000);
}
Best Tutorial to understand horizontal scroll - CLICK HERE
My Android app has a game map with markers on it. When the user taps a marker, I'm displaying a PopupWindow aligned with the map marker (similar to Google Maps). The problem I'm having is if a marker is close to the top of the screen, the PopupWindow overlaps with the ActionBar (and under the status bar, if the marker is high enough).
I'm displaying the PopupWindow by calling showAtLocation(), which I'd hoped would constrain the view to inside the "Map" fragment (it does on the left and right sides), but that's not working.
I had already implemented an adjustment, to account for text inside the popup taking up more than one line, where I update the Y position of the popup after the View has been laid out. That worked with no problem, but when I tried to add another vertical adjustment for this situation, the PopupWindow's position does not change.
Here is the code for the PopupWindow implementation:
/**
* Displays a PopupWindow.
*/
public class MyPopupWindow extends PopupWindow
{
private Fragment m_fragment;
private int m_x;
private int m_y;
/**
* Displays a PopupWindow at a certain offset (x, y) from the center of fragment's view.
*/
public static MyPopupWindow createPopup (Context context, Fragment fragment, int x, int y)
{
// Create the PopupWindow's content view
LayoutInflater inflater = LayoutInflater.from (context);
View popupView = inflater.inflate (R.layout.view_map_popup, null);
MyPopupWindow popupWindow = new MyPopupWindow (popupView, x, y);
popupWindow.m_fragment = fragment;
popupWindow.showAtLocation (fragment.getView (), Gravity.CENTER, x, y);
return popupWindow;
}
private MyPopupWindow (View view, int x, int y)
{
super (view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
m_x = x;
m_y = y;
TextView title = (TextView) view.findViewById (R.id.lblMapPopupTitle);
title.setText ("Exercise Title");
TextView description = (TextView) view.findViewById (R.id.lblMapPopupDescription);
description.setText ("Exercise Description\nSecond Line\nThird Line\nFourth Line\nAnd one more...");
view.setVisibility (View.INVISIBLE);
view.addOnLayoutChangeListener (layoutChangeListener);
}
// If the tapped map location is too close to the edge, determine the
// delta-x and delta-y needed to align it with the PopupWindow
private int getHorizontalAdjustment (int popupWidth)
{
int horizontalAdjustment = 0;
int parentWidth = m_fragment.getView ().getWidth ();
if ((parentWidth / 2) + m_x + (popupWidth / 2) > parentWidth)
{
horizontalAdjustment = parentWidth - ((parentWidth / 2) + m_x + (popupWidth / 2));
}
else if ((parentWidth / 2) + m_x - (popupWidth / 2) < 0)
{
horizontalAdjustment = 0 - ((parentWidth / 2) + m_x - (popupWidth / 2));
}
return horizontalAdjustment;
}
private int getVerticalAdjustment (int popupHeight)
{
int verticalAdjustment = 0;
int parentHeight = m_fragment.getView ().getHeight ();
int y = m_y - (popupHeight / 2);
if ((parentHeight / 2) + y + (popupHeight / 2) > parentHeight)
{
verticalAdjustment = parentHeight - ((parentHeight / 2) + y + (popupHeight / 2));
}
else if ((parentHeight / 2) + y - (popupHeight / 2) < 20)
{
verticalAdjustment = 20 - ((parentHeight / 2) + y - (popupHeight / 2));
}
return verticalAdjustment;
}
private View.OnLayoutChangeListener layoutChangeListener = new View.OnLayoutChangeListener ()
{
#Override
public void onLayoutChange (View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)
{
int height = bottom - top;
int width = right - left;
// Adjust the y-position for the inflated height of the view
// (so the popup's bottom edge lines up with the tapped marker's top edge)
int y = m_y - (height / 2);
int x = m_x;
// Determine any adjustments that need to be made to line up the tapped marker with the popup
int horizontalAdjustment = getHorizontalAdjustment (width);
int verticalAdjustment = getVerticalAdjustment (height);
y -= verticalAdjustment;
// Update our position with the re-calculated position
update (x, y, -1, -1);
view.setVisibility (View.VISIBLE);
}
};
}
The issue can be seen in this screenshot: PopupWindow overlaps ActionBar
Basically, I need to either (1) get the PopupWindow to clip to the Fragment's view and not overlap the ActionBar, or (2) get the PopupWindow to update its position in the event that it does overlap the ActionBar.
Any help is greatly appreciated.
i am trying to built the quick Action in my project. when i click the view it show the pop up window properly. But the arrow(arrow up, arrow down) which is point to the Parent View is not showing. i have tried it in many ways. if some one have the better solution or good logic please help me
thanks:
Here you can see the code:
public void show (View anchor) {
preShow();
int[] location = new int[2];
anchor.getLocationOnScreen(location);
Rect anchorRect = new Rect(location[0], location[1], location[0] + anchor.getWidth(), location[1]
+ anchor.getHeight());
mRootView.setLayoutParams(new LayoutParams(300, 400));
mRootView.measure(200,300);
int rootWidth = mRootView.getMeasuredWidth();
int rootHeight = mRootView.getMeasuredHeight();
int screenWidth = mWindowManager.getDefaultDisplay().getWidth();
int xPos = (screenWidth - rootWidth) / 2;
int yPos = anchorRect.top - rootHeight;
boolean onTop = true;
if (rootHeight > anchor.getTop()) {
yPos = anchorRect.bottom;
onTop = false;
}
if(onTop==true){
showArrow(R.id.arrow_down, anchorRect.centerX());
}
else
{
showArrow( R.id.arrow_up, anchorRect.centerX());
}
and showArrow method is as:
private void showArrow(int whichArrow, int requestedX) {
final View showArrow;
final View hideArrow;
if(whichArrow==R.id.arrow_down)
{
showArrow=mArrowDown;
hideArrow=mArrowUp;
}
else{
showArrow=mArrowUp;
hideArrow=mArrowDown;
}
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);
}
There is nothing wrong with you code.Probably,some thing will be wrong in your xml file. Please review you xml file. Try to modify you xml file. Hopefully,your problem will be resolved.
Does someone know about Android animations? I want to create something like following:
I have a big image in the center of my device screen;
this image becomes small (by animation) and goes to the corner of my device screen;
Its something like in this bellow sequence:
Any hints would be very appreciated! Thanks in advance!
Use ViewPropertyAnimator, with methods like scaleXBy() and translateYBy(). You get a ViewPropertyAnimator by calling animate() on a View, on API Level 11+. If you are supporting older devices, NineOldAndroids offers a near-workalike backport.
You might also wish to read:
http://android-developers.blogspot.com/2011/05/introducing-viewpropertyanimator.html
http://developer.android.com/guide/topics/graphics/prop-animation.html
I have a class with the simultaneous rotation and movement. It's costly but it works on all API versions.
public class ResizeMoveAnimation extends Animation {
View view;
int fromLeft;
int fromTop;
int fromRight;
int fromBottom;
int toLeft;
int toTop;
int toRight;
int toBottom;
public ResizeMoveAnimation(View v, int toLeft, int toTop, int toRight, int toBottom) {
this.view = v;
this.toLeft = toLeft;
this.toTop = toTop;
this.toRight = toRight;
this.toBottom = toBottom;
fromLeft = v.getLeft();
fromTop = v.getTop();
fromRight = v.getRight();
fromBottom = v.getBottom();
setDuration(500);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float left = fromLeft + (toLeft - fromLeft) * interpolatedTime;
float top = fromTop + (toTop - fromTop) * interpolatedTime;
float right = fromRight + (toRight - fromRight) * interpolatedTime;
float bottom = fromBottom + (toBottom - fromBottom) * interpolatedTime;
RelativeLayout.LayoutParams p = (LayoutParams) view.getLayoutParams();
p.leftMargin = (int) left;
p.topMargin = (int) top;
p.width = (int) ((right - left) + 1);
p.height = (int) ((bottom - top) + 1);
view.requestLayout();
}
}