Here is a picture of what I'm trying to do:
So, I want to resize a single cell while dragging resize anchors (black quads) which are ImageViews. To do this I attached custom onTouchListener to them that does next in MotionEvent.ACTION_MOVE:
calculate drag offset
set cell height/width based on this offset
reposition anchor point by changing it's layout params
The outcome of this is that the cell resizes but there is some king of flicker, more like shaking left/right or up/down, by some small offset.
My guess is that the problem comes when it catches move event, then I manualy change position of anchor and then, when it catches move event again it doesn't handle that change well...or something
I have an idea to put invisible ImageViews under each anchor and do resize based on their movement but do not move that anchor while draging. Then when I relese it, it lines up with coresponding visible anchor. But this is more hacking than solution :)
And finally, does anubody know why is this happening?
EDIT:
Here is the code where I'm handlign move event:
float dragY = event.getRawY() - resizePreviousPositionY;
LinearLayout.LayoutParams componentParams = (LinearLayout.LayoutParams)selectedComponent.layout.getLayoutParams();
LinearLayout parrent = (LinearLayout)selectedComponent.layout.getParent();
View topSibling = parrent.getChildAt(parrent.indexOfChild(selectedComponent.layout) - 1);
LinearLayout.LayoutParams topSiblingParams = (LinearLayout.LayoutParams)topSibling.getLayoutParams();
if(dragY > 0 && selectedComponent.layout.getHeight() > 100 ||
dragY < 0 && topSibling.getHeight() > 100)
{
componentParams.height = selectedComponent.layout.getHeight() - (int)dragY;
topSiblingParams.height = topSibling.getHeight() + (int)dragY;
//bottomSiblingParams.height = bottomSibling.getHeight();
selectedComponent.layout.setLayoutParams(componentParams);
repositionResizeAnchors(selectedComponent);
}
resizePreviousPositionY = event.getRawY();
and here is where I reposition it:
if(((LinearLayout)viewHolder.layout.getParent()).getOrientation() == LinearLayout.VERTICAL)
{
leftMargin = ((LinearLayout)viewHolder.layout.getParent()).getLeft() +
viewHolder.layout.getLeft() + viewHolder.layout.getWidth()/2;
}
else
{
leftMargin = viewHolder.layout.getLeft() + viewHolder.layout.getWidth()/2;
}
topMargin = viewHolder.layout.getTop();
params = (RelativeLayout.LayoutParams)resizeTop.getLayoutParams();
params.leftMargin = leftMargin - dpToPx(resizeAnchorRadius/2);
params.topMargin = topMargin - dpToPx(resizeAnchorRadius/2);
resizeTop.setLayoutParams(params);
Related
I am trying to change Google map V2 camera center position to be lower to the center of the map view.
So far, I've partially succeeded to achive this with the help of a few solutions such as this
This solution did the job but the actual camera position still remains in the center of the map!
It "pushed" the target location down but the camera still "looking" on the center of the MapView.
The problem with this approach comes when you want do animations and map movement such in navigation.
You need to calculate the offset for every location you get from the GPS and when you do hard turns the offset is not the same as Camera animation route is not in my control .
For instance, if I move tward some direction on foot and then suddenly I turn 180 degrees, the animation will push my target location until the animation complete and then it will look ok, thats because the the map rotation and animation is always around the cam position which is always in the center of the view.
Fo instance, in Google Map Navigation - The Nav marker is at the bottom of the map, and it seems to rotate and animate around that position and not around the view center.
Here is a screen shot the result i've got from above solutions:
What I want is that the camera position will be in my target position.
Google map navigation:
You should use Map Padding, namely the setPadding(int left, int top, int right, int bottom) method:
...
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
mGoogleMap.setPadding(0, dHeight, 0, 0);
...
}
...
where dHeight = Target_pos.y - cam_pos.y. And you should move compass button, like in this answer of Vignon with some changes for drawing compass outside of clipping region:
try {
final ViewGroup parent = (ViewGroup) mMapFragment.getView().findViewWithTag("GoogleMapMyLocationButton").getParent();
parent.post(new Runnable() {
#Override
public void run() {
try {
Resources r = getResources();
//convert our dp margin into pixels
int marginPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, r.getDisplayMetrics());
// Get the map compass view
View mapCompass = parent.getChildAt(4);
View v = mapCompass;
while (v.getParent() != null && v.getParent() instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) v.getParent();
viewGroup.setClipChildren(false);
viewGroup.setClipToPadding(false);
v = viewGroup;
}
// create layoutParams, giving it our wanted width and height(important, by default the width is "match parent")
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(mapCompass.getHeight(),mapCompass.getHeight());
// position on top right
rlp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
rlp.addRule(RelativeLayout.ALIGN_PARENT_TOP, 0);
rlp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0);
rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0);
//give compass margin
rlp.setMargins(marginPixels, dHeight, marginPixels, marginPixels);
mapCompass.setLayoutParams(rlp);
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
Also you can try that dirty solution (see the picture)
is to scale height of MapFragment (MapView) to dHeight more then visible area (screen height), where:
dHeight = (int) (k * MapFragment.height)
where k = Target_pos.y / cam_pos.y . In that case map center moved exactly to your desired point (but bottom (white on picture) part of map will not be visible). Also you should move up zoom controls, because by default it also at the bottom of map and will not be visible too. You can do this like in this answer. But remember, that as per the Google Maps APIs Terms of Service, your application must not remove or obscure the Google logo or copyright notices.
Seems the right solution is to create custom view, which extends MapFragment or MapView, e.g. like in that answer and do all your every location offset calculate magic inside it.
Finally I've came to the solution!!
The padding solution is good for a static map but when you want to do continuance animation on the map like in navigation, the padding causes to un expected movement in turns, especially when the map tilts and zoomed!!
The MapView size solution is the right choice here as it simply push the center of the map.
The problem with that approach is that it also pushes down the zoom control and most important the Google logo which must be visible.
So, you have to explicitly set the the zoom control and Google logo bottom margin back so they will be visible again! This without putting any padding to the map view itself.
Alternatively you can set the controls and the logo position to different corners if it's ok for your need, I've decided just to set their margins.
Here is my code:
final ViewTreeObserver observer = mapView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mainParentLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int parentHeight = ((ViewGroup)mapView.getParent()).getHeight() - toolbar.getHeight();
int mapHeight = parentHeight + parentHeight/2;
mapView.getLayoutParams().height = mapHeight;
int margin = mapHeight - parentHeight + 2*((int)(getResources().getDimension(R.dimen.small_image) + getResources().getDimension(R.dimen.small_pargin)));
Log.i(TAG, "onGlobalLayout: parent height: " + parentHeight + ", map new height: " + mapHeight + ", margin: " + margin);
setMapControlsMargin(margin);
}
});
This code, waits for the map view to be shown, then it calculate the map parent view height (in my case a ConstraintLayout) then it set the map height to be parentHeight * 1.5. This will push the camera center down!
Then, set the margin the Google logo and zoom controls.
private void setMapControlsMargin(final int margin){
try {
//Find the Zoom controls parent
final ViewGroup parent = (ViewGroup) mapView.findViewWithTag("GoogleMapMyLocationButton").getParent();
//Find the Google logo parent view group
final ViewGroup googleLogo = (ViewGroup) mapView.findViewWithTag("GoogleWatermark").getParent();
Log.i(TAG, "setMapControlsMargin: found Google logo " + (googleLogo != null));
//Set the margin for the Google logo
if (googleLogo != null) {
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) googleLogo.getLayoutParams();
layoutParams.bottomMargin = margin;
}
parent.post(() -> {
try {
View zoomControls = parent.getChildAt(2); //Zoom controls
ViewGroup.LayoutParams layoutParams1 = zoomControls.getLayoutParams();
//Read the zoom controls original margin, this will be added to the offset margin
int origBotMargin = ((RelativeLayout.LayoutParams) layoutParams1).bottomMargin;
int origRightMargin = ((RelativeLayout.LayoutParams) layoutParams1).rightMargin;
// create layoutParams, giving it our wanted width and height(important, by default the width is "match parent")
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(zoomControls.getWidth(),zoomControls.getHeight());
// position on bottom right
rlp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
//set the new margin
rlp.setMargins(0, 0, origRightMargin, origBotMargin + margin);
zoomControls.setLayoutParams(rlp);
} catch (Exception ex) {
ex.printStackTrace();
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
I was helped this post to move the Google logo.
Recently I've started working on custom scroll for RecyclerView but I've encountered a problem. I did header parallax and it worked great, but then I wanted to make rows expand when you scroll. For this method I needed to use recyclerView.computeVerticalScrollOffset() it works fine for the first two items in the list but then it falls apart.
Here is an example of my code:
FrameLayout main = (FrameLayout) holder.itemView;
if (main != null) {
int height = calculateHeight(offset, (position + i));
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) main.getLayoutParams();
lp.height = height;
main.setLayoutParams(lp);
}
So I have a function that calculates the height based on the position in layout and offset of the recyclerview. When the second item reaches Y == 0 the offset drops for half (i.e. it drops from 1200 to 600 and then it goes on but the calculations are all wrong)
Any ideas on how to get a stable offset value or how to change it with something that is consistent with recyclerView position.
EDIT:
OK, I can describe the problem step by step:
Element at position(0) of the adapter has a height of 1200
All other elements have the default height of 300
When user starts to scroll down, the element at position(1) starts to expand from 300 to 1200 height
--------- This is where the code starts to crack... -----------
When element at position(1) with the new height of 1200 reaches the top of the screen (when getTop() == 0) offset here is around 900.
When this same element starts to get off the screen (user is scrolling down) the offset at the point where top screen and this element collides (0,0) breaks down by the half, so offset is now 450 when it should be 900, and then it goes from 450 on, but my element at position(1) is now only half the size it should be...
OK I created a workaround for this solution and I am posting this as answer, if someone needs it in the future.
Calculate your own offset:
public int offsetForVerticalScrolling(RecyclerView recyclerView) {
LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
int position = llm.findFirstVisibleItemPosition();
ViewHolder mViewHolder = (ViewHolder) recyclerView.findViewHolderForAdapterPosition(position);
View item = mViewHolder.itemView;
int y = (int) item.getY();
if (y < 0) y *= -1;
if (position == 0) return y;
else {
int offset = y;
for (int i = 0; i < position; i++) {
//Add your previous item heights to offset
}
return offset;
}
}
I have a RelativeLayout (params = MatchParent,MatchParent) as my content view.
The RelativeLayout contains a ScrollView (params=Wrap_Content, Wrap_Content).
The ScrollView then contains a LinearLayout ( no params, set vertical orientation).
To the LinearLayout I'm adding "myrows".
Each myrow is made up of another LinearLayout (no params, horizontal orientation) which contains a SpannableString which contains equally spaced/padded numbers.
The result is a vertically scrollable table, and looks ok. The problem is that if I run it on a smaller device, the SpannableStrings extend out of the screen.
On the smaller screen I want the text to be large enough to read, and so I need to be able to scroll horizontally as well to see the part of the SpannableString which has extended beyond screen. I have tried a couple of solutions already. One was to put the vertical ScrollView inside a horizontal ScrollView and capture the xy etc...but people mentioned this is dangerous. I don't want the strings to wrap. I can't use a dropbox etc...to show the beyondcreen values. It has to be a flat table with text large enough to read, I can't eliminate any of the string length.
I can make it instead a horizontal ScrollView and put a page up/down button but it won't work as smooth as it does now. Could I put a ListView inside a horizontal ScrollView and be able to scroll in both directions?
Any other ideas for this? Is there a scroll bar I can put at the bottom that scrolls the screen horizontally where it only captures your finger touch on the bar itself?
Thanks!
I finally found a solution which works great. Create a custom gridView with the following overrides. I also created a scrolling header which follows the scroll of the gridView.
#Override
public boolean onInterceptTouchEvent (MotionEvent ev) {
getMaxLeftScroll();
myLastX = ev.getX();
myLastY = ev.getY();
return super.onInterceptTouchEvent(ev);
}
#Override
public boolean onTouchEvent(final MotionEvent ev){
final int action = ev.getAction();
final float x;
final float y;
switch (action) {
case MotionEvent.ACTION_MOVE:
// This handles Scrolling only.
x = ev.getX();
y = ev.getY();
final int deltaX = (int) (myLastX - x);
final int deltaY = (int) (myLastY - y);
if(Math.abs(deltaX) > Math.abs(deltaY)) {
if( (iCurrentLeftScroll+deltaX) > 0 &&
iCurrentLeftScroll+deltaX < iMaxLeftScroll) {
scrollBy(deltaX, 0);
//Listener and call the method here to scroll the header
mScrollChangeListener.onGridViewScrolled(deltaX);
iCurrentLeftScroll+=deltaX;
} //Scrolled by some value
// Close to right edge within 10? Smooth Scroll remaining small distance if not already there
else if(deltaX > 0 && (iCurrentLeftScroll<iMaxLeftScroll)) {
if ((iMaxLeftScroll - iCurrentLeftScroll) < 10) {
scrollBy(iMaxLeftScroll-iCurrentLeftScroll, 0);
mScrollChangeListener.onGridViewScrolled(iMaxLeftScroll-iCurrentLeftScroll);
iCurrentLeftScroll = iMaxLeftScroll;
}
}
// Close to left edge within 30? Smooth Scroll remaining small distance if not already there
else if(deltaX < 0 && (iCurrentLeftScroll>0)) {
if ((iCurrentLeftScroll) < 10) {
scrollBy(iCurrentLeftScroll*-1, 0);
mScrollChangeListener.onGridViewScrolled(iCurrentLeftScroll*-1);
iCurrentLeftScroll = 0;
}
}
//left and right are subjective
if(iCurrentLeftScroll == iMaxLeftScroll)
iScrollStatus = SCROLLED_FULL_LEFT;
if(iCurrentLeftScroll == 0)
iScrollStatus = SCROLLED_FULL_RIGHT;
if( (iCurrentLeftScroll > 0) && (iCurrentLeftScroll < iMaxLeftScroll) )
iScrollStatus = 0;
Log.d(TAG," Scroll Status " + String.valueOf(iScrollStatus) );
Log.d(TAG," MaxLeftScroll " + String.valueOf(iMaxLeftScroll) );
Log.d(TAG," Current Left Scroll " + String.valueOf(iCurrentLeftScroll) );
}
myLastX = x;
myLastY = y;
break;
}
return super.onTouchEvent(ev);
}
I'm trying to make an imageview draggable using onTouchListener. This is the code i have so far for the drag
switch (ME.getAction()){
case MotionEvent.ACTION_DOWN://IF USER PRESSED ON THE IMAGE
origix = v.getLeft(); //save original x coordinate calculated with the offset
origiy = v.getTop();
break;
case MotionEvent.ACTION_MOVE://IF USER IS MOVING THE IMAGE
System.out.println("MOVED");
int x_cord = (int)ME.getRawX(); //get coordinate of the user touch
int y_cord = (int)ME.getRawY();
//set the image to the new coordinates based on where the user is touching and dragging
MarginLayoutParams marginParams = new MarginLayoutParams(v.getLayoutParams());
marginParams.setMargins(x_cord - origix, y_cord - origiy,0, 0);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(marginParams);
v.setLayoutParams(layoutParams);
MOVED = true; //mark that the image has been move
break;
The variable 'v' is the imageview. The issue im getting is that the getRawX and getRawY isnt returning the correct coordinates (i think...). When i try touch the image and then try to drag it, the image starts dragging from another position. Usually much higher and little more to the right than the original position. The imageview is in a RelativeLayout and i'm using API 10
When the move starts, you need to initialize an offset (i.e. x_offset and y_offset) with the initial position of the ImageView.
Then, the move should be modified as follows:
marginParams.setMargins(x_cord - x_offset, y_cord - y_offset,0, 0);
This will stop your drag from starting from a different position than your ImageView starts from.
needed to get the offset of the relativelayout and the screen
int offsety = (screenheight - relheight); //screenheight is the height of the screen and rel height is the height of the relativelayout that the image is in then changed
marginParams.setMargins(x_cord - origix, y_cord - origiy,0, 0); to this
marginParams.setMargins(x_cord - (v.getWidth()/2), y_cord - offsety - (v.getHeight()/2),0, 0);
I am trying to allow the user to drag and drop and image from on position to another. The screen layout is as follows:
1 2 3
4 5 6
7 8 9
I want the user to grab image 2, 4, 6, or 8 and drag it to image 5. Upon dragging to image 5 I want to load up a fragment. The user can only drag the image in a straight line from it's current position to 5's position. ie image 2 and only drag down and only until it is overtop of image 5, image 4 can only drag right until overtop of 5, etc.
Any insight on how to do this is greatly appreciated.
Thanks,
DMan
So I figured out a way to get it to work. Pretty hacky but I basically use the image positions and transition it via a margin in the direct that I want from it's center until it reaches the desired position.
I created a temp image (mTempImage) and hide my main image because the image had parameters set in the layout that forced it to stay above a specific item in the layout and wouldnt let it margin less than that items height.
Here is a sample from my onTouchListener
int eventX = (int)event.getX();
int eventY = (int)event.getY();
int movingViewHeight = view.getHeight();
int movingViewWidth = view.getWidth();
int destinationTileTop = mTileHere.getTop();
int destinationTileLeft = mTileHere.getLeft();
// Transparent 'Tile Here' image (1)
// Transitional 'Tile Here' image (0 - 1)
// Opaque 'Tile Here' image (0)
float alphaMultiplier = 0;
switch(view.getId()) {
case R.id.item_position_2:
// Only allow sliding down from center of object (positive eventY)
if (eventY > movingViewHeight / 2) {
// Calculate the amount that the image has moved from it's center point
int posFromImgCenter = (eventY - movingViewHeight / 2);
// Check if the image has reached the center point and stop it's motion any farther
if (posFromImgCenter >= destinationTileTop) {
params.setMargins(view.getLeft(), destinationTileTop, 0, 0);
alphaMultiplier = 1;
}
// Slide the image with the positioning of the persons finger
else {
params.setMargins(view.getLeft(), posFromImgCenter, 0, 0);
alphaMultiplier = ((float)posFromImgCenter / (float)destinationTileTop);
}
}
// Attempting to slide in an invalid direction leave the image where it is
else
params.setMargins(view.getLeft(), view.getTop(), 0, 0);
... MORE CODE HERE FOR OTHER ITEM POSITIONS
// AFTER SWITCH STATEMENT COMPLETE UPDATE VIEW ITEMS ACCORDINGLY
// default 0 if not set with valid movement
mTileHere.setAlpha((int)(255 - (255 * alphaMultiplier)));
mTempImage.setLayoutParams(params);
mTempImage.invalidate();
Thanks,
DMan