Android: Limit drag & drop of object - java

I have a Activity. On this Activity I have a Relative Layout. On this layout I have placed several custom objects from a class that extends RelativeLayout.
It kind of looks like a chessboard (it isn't, but so you know what I'm talking about).
I now want to be able to touch one of this objects and drag it to one of the 4 adjacent fields, swapping it against the one there.
I thought the most simple way would be to use androids drag & drop.
I used setOnTouchListener and setOnDragListener for every object, so every object can be touched and dragged onto another object.
I used this tutorial, should be easier for you to read than just the code, but here it is:
#Override
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(v);
v.startDrag(null, shadowBuilder, v, 0);
return true;
} else {
return false;
}
}
#Override
public boolean onDrag(View v, DragEvent e) {
if (e.getAction()==DragEvent.ACTION_DROP) {
View view = (View) e.getLocalState();
ViewGroup from = (ViewGroup) view.getParent();
from.removeView(view);
MyCustomViewClass to = (MyCustomViewClass) v;
to.addView(view);
view.setVisibility(View.VISIBLE);
}
return true;
}
Now I am wondering how I could limit where a object could be dropped. I want it to be able to be moved just to the 4 adjacent fields (not only by limiting the draglistener, I also don't want the object to be moved beyond this objects, no matter how far the finger tries to drag it).
I hope you get what I mean, if not please ask.
So this question is all about what I can do with this drag & drop I guess. Is it possible to somehow detect where the finger is before dropping? Is it possible to limit the dragging to the straight x and y axis? Or is there a way to know the direction of that dragging movement, allowing me to 1) know the object I would swap with and 2) moving the other object before the drop, making it look like the objects are just swapping?
Edit:
I got the direction, since I can calculate it from view.getX, view.getY (coords are inside my startobject) and v.getX and v.getY (coords from the object I'm over) in onDrag when the dragEvent is ACTION_ENTERED.
Now I try to figure out either how to set the dragshadows position to the object I want it to be over, or to only allow dragging in my direction and ending the drag over the next object.
Further help or ideas still appreciated.

Related

Scroll a list while the user is dragging an object near the bounds

I have a problem that I have been unable to solve in a way that I am very happy with.
I have a view that I am dragging and dropping into a list. That list is created using a recyclerView. The drag object works fine, and the recyclerView's items can all receive the events no problem. Now I want to make the list scroll as the user drags their finger close to the top or bottom of the list. My first step was to add a dragEvent listener to the recyclerView, and attempt to start scrolling each time I got a location near the top or bottom edge. So, my DragEvent.Location case looks something like this:
case DragEvent.ACTION_DRAG_LOCATION: {
removeDragScrollCallBack();
float y = event.getY();
final int scrollAreaHeight = v.getHeight()/4;
final int delayMills = 16;
int scrollAmount = 0;
if (y > v.getHeight() - scrollAreaHeight) {
scrollAmount = 10;
} else if (y < scrollAreaHeight) {
scrollAmount = -10;
}
if (Math.abs(scrollAmount) > 0) {
final int finalScrollAmount = scrollAmount;
dragScrollRunnable = new Runnable() {
#Override
public void run() {
if (canScrollVertically(finalScrollAmount)) {
scrollBy(0, finalScrollAmount);
if (dragScrollHandler != null && dragScrollRunnable != null) {
dragScrollHandler.postDelayed(this, delayMills);
}
}
}
};
dragScrollRunnable.run();
}
return true;
}
It kinda works. Things scroll in the right direction. It seems to sputter a bit though, and generally not scroll very smoothly. Additionally, the drag and drop drop event sometimes doesn't make it to the children while the recycler view is still scrolling.
So, I went to the google example of doing a similar thing in a using a list view - link. I modified the code they used for their list view and tried to handle my recyclerView in a similar manner. This had even poorer results for me.
I have tried various other alterations of these techniques, and swapped to using the smoothScroll function instead of the standard scroll function, but I'm not too happy with any of the results.
Does anyone have a good solution for how to handle this?
Update: I now believe that many of my problems with this functionality are due to the drag listener being fairly unreliable. At sometimes the recycler fails to get events when it's children are receiving events.
Turns out the drag listener on a view is not terribly reliable. At random times as I moved my finger around the screen, the drag listener wouldn't recieve all of the events. I believe the reason for this was the way that the children of the recyclerView were also recieving the on drag callbacks. The solution was to do what I had tried originally, but through a listener on the fragment itself. Now when I get an event, I check the coordinates to see what view it is in, and then convert it to local coordinates for that view. Then I can determine exactly how I need to handle it.

Get associated mouse click from ExpandTree event in SWT

I'm trying to stop the tree from collapsing or expanding when the user double clicks a column on a tree. It should only be allowed if the user clicks on the first column.
See, if a user double clicks the checkbox on node2 world1, the tree expands or collapses. I don't want that to happen. My tree needs SWT.FULL_SELECTION to detect the clicks on each of the columns, so that's not the way to go.
My listener looks like this
tree.addTreeListener(new TreeListener() {
#Override
public void treeExpanded(TreeEvent e) {
TreeItem parent = (TreeItem) e.item;
Point p = new Point (e.x, e.y);
int column = CheckboxClickListener.getColumn(p,parent);
if (column > 0) {
e.doit = false;
}
}
#Override
public void treeCollapsed(TreeEvent e) {
TreeItem parent = (TreeItem) e.item;
Point p = new Point (e.x, e.y);
int column = CheckboxClickListener.getColumn(p,parent);
if (column != 0) {
e.doit = false;
}
}
});
Problem is, the mouse event that generated the click is not the same as the TreeEvent that expands the tree. Thus, the e.x and e.y are both zero, making my Point detection useless. Listening to the mouse event and maintaining the last x and y to check here in the TreeExpand event seems bug-prone since the user may also expand the tree using the keyboard (thus the x and y may not reflect the user action). I also considered adding a time constraint to check that but seems like a bad way to handle the issue.
How can I detect which mouse event triggered the expand event?
PS: e.doit=false does nothing, even outside the if condition, so help with stopping the tree from expanding/collapsing would be appreciated as well :)
Thank you!
I found someone saying this is a bug at this link http://www.eclipse.org/forums/index.php/t/257325/
The following code stops the tree from expanding on doubleclick but I'm not sure why or what are the side effects.
tree.addListener (SWT.MeasureItem, new Listener(){
#Override
public void handleEvent(Event event) {}
});
This stops the expanding when doubleclicking ANY column. Clicking on the small arrow at the left of the TreeItem still expands the tree (as it should).

AndroidPlot: Detecting touch event on point

I'm writing an application using AndroidPlot in which the user needs to be able to touch a point on a scatter plot and bring up information about that specific point. In other words, the application needs to identify the closest point to the location touched or otherwise recognize that a point has been touched, and be able to return the specific identity of the point. All of the points in this scatter plot will always be of one series, so identifying between series isn't an issue, but I don't know how to implement finding or identifying the touched point.
I can get as far as:
plot.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
PointF click = new PointF(motionEvent.getX(), motionEvent.getY());
if(plot.getGraphWidget().containsPoint(click)) {
AlertDialog.Builder builder = new AlertDialog.Builder(GraphView.this);
builder.setTitle("Point: ");
builder.setMessage("Description: ");
AlertDialog dialog = builder.create();
dialog.show();
}
return false;
}
});
}
Which creates the AlertDialog whenever the graph is touched.
The DemoApp's BarPlotExampleActivity has this functionality implemented in it's onPlotClicked(...) method. It could certainly be improved but should give you a good starting point.
The basic steps are:
Filter for clicks within the plot's graph area. (You've got this piece already)
Convert the screen coords to domain/range values using XYPlot.getXVal(screenX) & XYPlot.getYVal(screenY).
Find the closest point(s) in your model for the above domain/range values.

Focus ScrollView to selected position programmatically - Android

I have a ScrollView with a lot buttons. Each button is enabled when the user unlocks that button/level. I would like to focus the ScrollView on the latest unlocked button/level. See the screenshot below.
I found some functions like scrollTo() that could focus the ScrollView either at top, button or something like that but I would like to focus the ScrollView at certain places like the button ID that says Level 8 in the screenshot below. How would I go about doing this?
I think this should be good enough :
yourButtonView.getParent().requestChildFocus(yourButtonView,yourButtonView);
public void RequestChildFocus (View child, View focused)
child - The child of this ViewParent that wants focus. This view will contain the focused view. It is not necessarily the view that actually has focus.
focused - The view that is a descendant of child that actually has focus
First you need to know the position of item in scroll that has to get focus once you know that you can use following code to make that item focus
final int x;
final int y;
x = rowview[pos].getLeft();
y = rowView[pos].getTop();
yourScrollView.scrollTo(x, y);
refer this question
I agree with the benefits of previous answers. However, by experience, I have found this to be more complex than this. The setSelectionFromTop is very sensitive and often breaks if it is executed too early. This may depend on different reasons but the two primary reasons are that
If executed from within Activity lifecycle methods the views have not been loaded/configured yet.
View modifications triggered after the list move action seems to break the move. Probably overwriting some value before the move has been finalized due to a recalculation of the views.
The reasoning seems to apply for both setSelectionFromTop and setSelection() methods although
I tested mostly with setSelectionFromTop. smoothScrollToPosition() seems to be more robust, probably because it by definition changes the list position delayed whn doing the smooth scrolling.
Look at this example source code:
// Wee need to pospone the list move until all other view setup activities are finished
list.post(new Runnable(){
#override
public void run() {
list.setSelectionFromTop(selectedPosition, Math.max(0, Math.round(list.getHeight() / 3))); // Make sure selection is in middle of page, if possible.
// Make sure you do not modify (this or other) parts of the view afterwards - it may break the list move
// activityButtons.setVisibility(View.GONE);
}});
see: http://developer.android.com/reference/android/widget/ListView.html#setSelectionFromTop(int, int)
'int selectedLevel' holds the currently selected level, information is held in a Level class.
'values' is a list with all your levels, displayed by the list.
Example:
ListView lv = (ListView)findViewById(android.R.id.list);
int i = 0;
for (Level l : values) {
i++;
if (l.level == selectedLevel) {
lv.setSelectionFromTop(i, 200);
break;
}
}
try this
sc.post(new Runnable() {
public void run() {
sc.smoothScrollTo(x, y);
}
});
x the position where to scroll on the X axis
y the position where to scroll on the Y axis
Use this code, taken from this answer
private final void focusOnView(){
new Handler().post(new Runnable() {
#Override
public void run() {
your_scrollview.scrollTo(0, your_EditBox.getBottom());
}
});
}
Use this code, taken from my another answer
scrollViewSignup.post(new Runnable() {
#Override
public void run() {
int scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, 0);
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, true);
int new_scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, scrollY);
scrollViewSignup.smoothScrollTo(0, new_scrollY);
}
});
This code tries to smooth scroll and uses the standard behaviour of system to position to an item. You can simply change smoothScrollTo with scrollTo if you don't want the system to smooth scroll to the item. Or, you can use the code below only.
scrollViewSignup.post(new Runnable() {
#Override
public void run() {
scrollViewSignup.scrollTo(0, 0);
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, true);
}
});
Use the desired code block after trying, or, according to the need.

Should I implement actions inside the methods of an InputListener object?

I am making a button using MouseOverArea. After some trial and error, I realized I can override the methods in InputListener to do particular actions when an input event is notified.
For example, do things when mouse left button is pressed while cursor is over the component.
#Override
public void mousePressed(int button, int mx, int my) {
if (isMouseOver() && button == Input.MOUSE_LEFT_BUTTON) {
// Some magic happens
}
}
However, I will not able to do things like changing current game state because no Game object around. I know there are many ways to solve this problem, but I would like to know what is the Slick way to do this.
Are these methods suitable for such behavior ?
One way to modify game states is to use boolean states; Which are boolean variables that hold the state of the game or player. For example:
boolean isMovingUp, isMovingLeft, isMovingRight, isMovingDown;
You can then set these to true/false depending on what mouse or keyboard event takes place and your game class then read these variables, like so:
if (isMovingUp) {
// do something
isMovingUp = !isMovingUp;
}
Hope that helps!

Categories

Resources