I am pretty new to Android and I am making an app with a Vertical Linear Layout at the bottom of the screen with a list of imageViews going across the screen. Each of these are draggable onto the main part of the screen, however, if I drag one of the imageviews on the far right of the layout, it will drop the view far to the left of the actual drop point. What is strange is that if I try dragging the first imageview in the linearlayout (the one on the left side) it will land right under where I drop it.
My onDrag and onTouch Listeners...
class MyDragListener implements OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
float X = event.getX();
float Y = event.getY();
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
break;
case DragEvent.ACTION_DRAG_ENTERED:
break;
case DragEvent.ACTION_DRAG_EXITED:
break;
case DragEvent.ACTION_DROP:
View view = (View) event.getLocalState();
ViewGroup owner = (ViewGroup) view.getParent();
owner.removeView(view);
container = (RelativeLayout) v;
container.setLayoutParams(findViewById(R.id.drawingbackground).getLayoutParams());
container.addView(view);
view.setX(X-(view.getWidth()/2));
view.setY(Y-(view.getHeight()/2));
view.setVisibility(View.VISIBLE);
break;
case DragEvent.ACTION_DRAG_ENDED:
default:
break;
}
return true;
}
}
and the OnTouch...
final class MyTouchListener implements OnTouchListener {
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
ClipData data = ClipData.newPlainText("", "");
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(data, shadowBuilder, view, 0);
return true;
} if(motionEvent.getAction()== MotionEvent.ACTION_UP){
view.performClick();
}
return false;
}
}
Any assistance would be greatly appreciated!
Change your xml.Use Relative Layout Instead of LinearLayout and Fix their height and width.It would work pretty fine.
Related
Solved it by using emandt's suggestion. My personal Solution added below.
I'm using Android Studio for this.
I searched for solutions but couldn't find anything resembling this.
I want to know on which ImageView an UP action occurs while starting the DOWN action on a different ImageView (to eventually be able to drag one image over the other and make it snap to the same position by getting the position of the image I dragged over).
My example has two ImageViews with the id imageView (left) and imageView2(right).
In my example I'm not dragging anything yet, I just want to touch the left image, see "Action was down" in the log and lift the finger over the right image showing "Action was up2".
I don't know if this is easily possible.
As far as I can tell from testing, the MotionEvent.ACTION_UP only fires for an ImageView when you also pressed down on it beforehand. So when I release on top of imageView2 it only shows "Action was up" from the left image.
I wondered if it was possible by playing with return false, since the return value tells if an ActionEvent is consumed so I thought if the UP event of imageView returns false, maybe it does trigger the UP event of imageView2 but no. (Either complete misunderstanding on my part or it doesn't recognise UP on the second because it didn't start with a DOWN and MotionEvents probably always have to start with a DOWN).
public class MainActivity extends Activity {
ImageView imageView;
ImageView imageView2;
String DEBUG_TAG = "action";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
imageView2 = findViewById(R.id.imageView2);
imageView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
//int action = MotionEventCompat.getActionMasked(event);
int action = event.getActionMasked();
switch(action) {
case (MotionEvent.ACTION_DOWN) :
Log.d(DEBUG_TAG,"Action was DOWN"+v.toString());
return true;
case (MotionEvent.ACTION_MOVE) :
//Log.d(DEBUG_TAG,"Action was MOVE");
return true;
case (MotionEvent.ACTION_UP) :
Log.d(DEBUG_TAG,"Action was UP"+v.toString());
return false;
default :
//return true;
}
return true;
}
});
imageView2.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
//int action = MotionEventCompat.getActionMasked(event);
int action = event.getActionMasked();
switch(action) {
case (MotionEvent.ACTION_DOWN) :
Log.d(DEBUG_TAG,"Action was DOWN2"+v.toString());
return true;
case (MotionEvent.ACTION_MOVE) :
//Log.d(DEBUG_TAG,"Action was MOVE");
return true;
case (MotionEvent.ACTION_UP) :
Log.d(DEBUG_TAG,"Action was UP2"+v.toString());
return true;
default :
//return true;
}
return true;
}
});
}
}
If there is no simple way to do this, I'm thinking about solving this mathematically, but maybe some of you can help.
So my question is, is there a way to recognise an UP action on a second ImageView while currently being in a MotionEvent of another ImageView?
SOLUTION (see emandt's answer)
I ditched the second OnClickListener because I realised that the 2nd image doesn't need any, I just need its position.
Added this method:
#Nullable
private View getDroppedView(View droppedView, int x, int y, List<View> arrayOfPossibilities) {
Rect cVisibleBoundsRect = new Rect();
for (View cView : arrayOfPossibilities) {
//if currently iterated view doesn't have values for getGlobalVisibleRect, skip the .contains part
//ignore the item which is your current active item (which would potentially be dropped)
//getGlobalVisibleRect sets cVisibleBoundsRect immediately to the Rect given as parameter
if (!cView.getGlobalVisibleRect(cVisibleBoundsRect)||(cView.equals(droppedView))) continue;
if (cVisibleBoundsRect.contains(x, y)) {
Log.d(DEBUG_TAG,"Found something");
//THIS "cView" IS THE VIEW WHERE YOU RELEASED THE FINGER
return cView;
}
}
Log.d(DEBUG_TAG,"Found nothing");
return null;
}
And added this in onUP:
case (MotionEvent.ACTION_UP) :
View dropTarget;
Log.d(DEBUG_TAG,"Action was UP"+v.toString());
dropTarget = getDroppedView(v, (int)event.getRawX(), (int)event.getRawY(), listOfViews);
if (dropTarget != null){
v.setX(dropTarget.getX());
v.setY(dropTarget.getY());
}
I think you want to know which is the View where you release the finger from the screen, am I right?
To do this you can use the same "View.OnTouchListener()" for all of your Views and in the ACTION_UP you have to call a new method similar to this (pseudo-code):
....
case (MotionEvent.ACTION_UP) :
View[] cArrayOfPossibileViews = new View[]{ findViewById(IMAGE_1), findViewById(IMAGE2) }
getDroppedView(v, event.getRawX(), event.getRawY(), cArrayOfPossibileViews);
break;
}
....
#Nullable
private View getDroppedView(View view, int x, int y, View[] arrayOfPossibilities) {
Rect cVisibleBoundsRect = new Rect();
for (View cView : arrayOfPossibilities) {
if (!cView.getGlobalVisibleRect(cVisibleBoundsRect)) continue;
if (cVisibleBoundsRect.contains(x, y)) {
//THIS "cView" IS THE VIEW WHERE YOU RELEASED THE FINGER
return cView;
}
}
return null;
}
This method get View bounds and compare them avains X and Y of your Touch Event. If X and Y are contained inside a View bounds it means that View is the one you need.
It is not related to changing order in RecyclerView question and I didn't find satisfying example.
Let's say we want to put apple to one of the boxes. In our application we have ImageView on the top which represents apple, and boxes below. These boxes are just RecyclerView with GridLayoutManager.
First we set onTouchListener on apple:
appleImageView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null, shadowBuilder, view, 0);
view.setVisibility(View.INVISIBLE);
return true;
} else {
return false;
}
}
});
Now we set OnDragListener on our container which contains RecyclerView (or can we set on RecyclerView?):
containerLayout.setOnDragListener(new OnDragListener() {
#Override
public boolean onDrag(View v, DragEvent event) {
final View draggedView = (View) event.getLocalState();
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
break;
case DragEvent.ACTION_DRAG_ENTERED:
break;
case DragEvent.ACTION_DRAG_EXITED:
break;
case DragEvent.ACTION_DROP:
// HERE IS INTERESTING PART
break;
case DragEvent.ACTION_DRAG_ENDED:
break;
default:
break;
}
return true;
}
});
The question is:
How can we get RecyclerView item on which drop action was happened?
I thought maybe we can get coordinates where we dropped appleImageView by calling event.getX(); event.getY(); but I don't how to get RecyclerView item with screen coordinates.
I have this drag and drop on android studio, it works using image views. Once I pick an image view up using a OnLongListener and drop it to a blank image view which is the target, the target image view should change to the image view I have grabbed.
My Code
img6.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
DragShadow dragshadow = new DragShadow(v);
ClipData data = ClipData.newPlainText("", "");
v.startDrag(data, dragshadow, v, 0);
return false;
}
});
droptarget.setOnDragListener(new View.OnDragListener() {
#Override
public boolean onDrag(View v, DragEvent event) {
int dragEvent = event.getAction();
int ImageViewID = v.getId();
switch (dragEvent) {
case DragEvent.ACTION_DRAG_ENTERED:
break;
case DragEvent.ACTION_DRAG_EXITED:
break;
case DragEvent.ACTION_DROP:
droptarget.setImageResource(ImageViewID);
break;
}
return true;
}
});
The setImageResource comes up as an error.
setImageResource sets a drawable as the content of an ImageView. But you are trying to pass the id of a view to it
int ImageViewID = v.getId();
droptarget.setImageResource(ImageViewID);
Instead you can try something:
ImageView imageView = (ImageView)v;
droptarget.setImageResource(imageView.getDrawable());
You can send the Drawable code(int) (For example int value of R.id.some_image_view) in the ClipData of the View getting dragged. Then use that code to retrieve the correct Drawable to the Dropped-on-View.
droptarget.setImageDrawable(getResources().getDrawable(Integer.parseInt(code)));
Then force a redraw with:
droptarget.invalidate();
This do require you to override the OnLongClick listener though with your own custom one.
I have this code in the ACTION_DOWN of my onTouch(View v) method as follows...
if (event.getAction() == MotionEvent.ACTION_DOWN) {
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null, shadowBuilder, view, 0);
myOnClickListener.onClick(view);
return true;
}
Now my view in this case is a ChessPiece. I have chessPiece.setBackground(R.color.black) and chessPiece.setImageBitmap(pawnBitmap).
Currently when I drag the chess piece, it drags the background color with it, which I dont want. So is there anyway to remove the background of the view when it's dragging so that the DragShadow is just of the bitmap?
You have to create your own dragshadow. This example helped me when I wanted to play with the dragshadow: http://lemonycode.blogspot.be/2012/11/using-custom-dragshadowbuidler-in.html
As you can see, there are two important methods:
public void onProvideShadowMetrics(Point shadowSize, Point touchPoint)
and:
public void onDrawShadow(Canvas canvas)
The first method is used to size the dragshadow and the second to actually draw your shadow. In your case, your chess piece. You could use
Bitmap bitmap = ((BitmapDrawable)image.getDrawable()).getBitmap();
to get the bitmap and alter it before drawing it on the canvas.
On OnDragListener()
private final class MyDragListener implements OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
getResources().getDrawable(android.graphics.Color.TRANSPARENT);
break;
....
}
return true;
}
}
This should work. Good luck!
Is there any way to disable getView() method in ListView Adapter when I use MotionEvent.ACTION_MOVE in OnTouchListener??
I need to do because i try move my conteiner(inside is my ListView) by finger. I use for this method setLayoutParams:
private class MyListener implements View.OnTouchListener{
#Override
public boolean onTouch(View v, MotionEvent event) {
final int Y = (int) event.getRawY();
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
RelativeLayout.LayoutParams paramsUp = (RelativeLayout.LayoutParams) myBar.getLayoutParams();
paramsUp.topMargin = 0;
myBar.setLayoutParams(paramsUp);
break;
case MotionEvent.ACTION_MOVE:
RelativeLayout.LayoutParams paramsMove = (RelativeLayout.LayoutParams) myBar.getLayoutParams();
paramsMove.topMargin = Y - yDelta;
myBar.setLayoutParams(paramsMove);
break;
case MotionEvent.ACTION_DOWN:
RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) myBar.getLayoutParams();
yDelta = Y - lParams.leftMargin;
break;
}
//myBar.invalidate();
return true;
}
}
When I moving container, everything happens very slowly. This is because the container changes its size and all objects are loaded again.
And my second question:
How to disbale scroll when I move ListView item? For example: move on left or move on right.
Thx