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.
Related
I want to create a dual side swipe button which gives haptic feedback on reaching the end. The image is attached.
https://i.stack.imgur.com/eIECf.png
This is not a question with a simple answer, but ill try to give you a general direction on how to go about implementing something like this:
You need handle Touch events on your button, yourself.
something like:
button.setOnTouchListener(new OnTouchListener()
{
#Override
public boolean onTouch(View arg0, MotionEvent arg1)
{
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
// touch down code
break;
case MotionEvent.ACTION_MOVE:
// touch move code
break;
case MotionEvent.ACTION_UP:
// touch up code
break;
}
return false;
}
});
In onTouch, you need to do two things:
Move the button to the appropriate location, base on the motion of the user's finger.
for this, you should probably store the position of ACTION_DOWN, and then calculate relative movement in ACTION_MOVE and move your button to match it.
If you want your button to only move along some axis, you need to calculate the appropriate position on this axis, yourself.
in ACTION_UP, check the position of your button, and if its close enough to where you want your swipe to end, execute the action
So I started working with ARCore in Android Studio in Java, and I tested their demo HelloAR, which works.
Now I want to add simple thing such as move the object to scroll direction.
In TapHelper I need to add onScroll for GestureDetector
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
}
But that is where I am stuck now, the virtual object is rendered based on anchor? So do I need to update the anchor position or the position of the virtual object? And how do I do that, the anchor does not have any way of updating its position, do I destroy it and create new one, or did I miss anything?
If you just want to rotate the renderable itself you don't need to create a new anchor, but if you want to move the renderable to a new place in the 'world' then the standard approach, at this time, seems to be to delete the anchor and recreate it.
Here is an example of how you might do this:
private AnchorNode moveRenderable(AnchorNode myAnchorNodeToMove, Pose newPoseToMoveTo) {
//Move a renderable to a new pose
if (myAnchorNodeToMove != null) {
arFragment.getArSceneView().getScene().removeChild(myAnchorNodeToMove);
} else {
Log.d(TAG,"moveRenderable - myAnchorNode was null");
return null;
}
Frame frame = arFragment.getArSceneView().getArFrame();
Session session = arFragment.getArSceneView().getSession();
Anchor myAnchor = session.createAnchor(newPoseToMoveTo.extractTranslation());
AnchorNode newMyAnchorNode = new AnchorNode(myAnchor);
newMyAnchorNode.setRenderable(andyRenderable);
newMyAnchorNode.setParent(arFragment.getArSceneView().getScene());
return newMyAnchorNode;
}
The above is modified from a working example to make it more readable here - full source is here: https://github.com/mickod/LineView
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.
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.
So, my issue is this: I'm attempting to define a custom set of nodes for a Javafx XYChart LineChart, where each node corresponds to a point that was plotted directly from the datasets. After looking around a little bit, Jewlesea actually had a solution at one point about how to add dynamic labels to nodes on a linechart graph that gave me enough of a push in the right direction to create black symbols (they are dots at the moment, but they can be many different things). Now I have a requirement that requires me to change ONE of the nodes on the XY chart into an 'X'. this could be either through loading an image in place of the 'node', or through physically manipulating the 'shape' parameter in .css.
The problem begins when I try to add this property dynamically, since which node has the 'x' will always be changing. Here are the things I've tried, and they all end up with no results whatsoever, regardless of the property used.
private XYChart.Data datum( Double x, Double y )
{
final XYChart.Data data = new XYChart.Data(x, y);
data.setNode(
new HoveredThresholdNode(x, y));
//data.getNode().setStyle("-fx-background-image: url(\"redX.png\");");
data.getNode().styleProperty().bind(
new SimpleStringProperty("-fx-background-color: #0181e2;")
.concat("-fx-font-size: 20px;")
.concat("-fx-background-radius: 0;")
.concat("-fx-background-insets: 0;")
.concat("-fx-shape: \"M2,0 L5,4 L8,0 L10,0 L10,2 L6,5 L10,8 L10,10 L8,10 L5,6 L2,10 L0,10 L0,8 L4,5 L0,2 L0,0 Z\";")
);
data.getNode().toFront();
return data;
}
So in the above, you can see that this is adding a property through the use of the 'bind' function after the dataNode has already been created. Also note above, I tried doing it through the 'setStyle' interface at this level to give it a background image, with no success. Also, no errors are being thrown, no 'invalid css' or anything of the sort, just simply no display on the graph at all when done this way.
now, in the HoveredThresholdNode (Again a big thanks to Jewelsea for being a master of Javafx and putting this bit of code online, it's where 90% of this class came from.) I tried essentially the same thing, at a different level. (actually being IN the node creation class, as opposed to a layer above it).
class HoveredThresholdNode extends StackPane {
/**
*
* #param x the x value of our node (this gets passed around a bunch)
* #param y the y value of our node (also gets passed around a bunch)
*/
HoveredThresholdNode(Double x, Double y) {
//The preferred size of each node of the graph
//getStylesheets().add(getClass().getResource("style/XYChart.css").toExternalForm());
//getStyleClass().add("xyChart-Node");
//setOpacity(.8);
styleProperty().bind(
new SimpleStringProperty("-fx-background-color: #0181e2;")
.concat("-fx-font-size: 20px;")
.concat("-fx-background-radius: 0;")
.concat("-fx-background-insets: 0;")
.concat("-fx-shape: \"M2,0 L5,4 L8,0 L10,0 L10,2 L6,5 L10,8 L10,10 L8,10 L5,6 L2,10 L0,10 L0,8 L4,5 L0,2 L0,0 Z\";")
);
//this label is the 'tooltip' label for the graph.
final Label label = createDataThresholdLabel(x, y);
final double Myx = x;
final double Myy = y;
setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
if (Myx == 0) {
label.setTextFill(Color.DARKGRAY);
} else if (Myx > 0) {
label.setTextFill(Color.SPRINGGREEN);
} else {
label.setTextFill(Color.FIREBRICK);
}
label.setText("Current position: " + Myx + " , " + Myy);
//setCursor(Cursor.NONE);
toFront();
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
//getChildren().clear();
//setCursor(Cursor.CROSSHAIR);
}
});
}
Now note, I also tried the setStyle(java.lang.String) method, with all of the same type of CSS, with no success. I have NO idea why this isn't styling dynamically. It's almost as if the custom nodes are simply ignoring all new .css that I define at runtime?
Any help would be greatly appreciated, please don't be shy if you need more details or explanation on any points.
So, I did finally find a good workaround to solve my problem, although not in the way I thought it would happen. The main problem I was having, was that I was extending from stackPane to create my node, which only had a very small number of graphical display options available to it, and by switching the 'prefSize()' property, I was simply changing the size of that stackPane, and then filling in the background area of that stack pane black, giving it a very deceptive shape-look to it.
So rather than use a stack pane, whenever I reached the node that I needed to place the red 'X' on, I simply called a different Datum method that returned a datum with an ImageView Attached, like so:
private XYChart.Data CoLDatum(Double x, Double y){
final XYChart.Data data = new XYChart.Data(x, y);
ImageView myImage = new ImageView(new Image(getClass().getResource("style/redX.png").toExternalForm()));
data.setNode(myImage);
data.getNode().setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
main_label.setText("Some Text.");
}
});
data.getNode().setOnMouseExited(new EventHandler<MouseEvent>(){
#Override public void handle(MouseEvent mouseEvent) {
main_label.setText("");
}
});
return data;
}
and since ImageView is an implementing class of Node, this worked out just fine, and allowed me to load up an image for that one single node in the graph, while still maintaining a listener to give custom text to our information label when the red 'x' was hovered over with a mouse. Sometimes, it's the simple solutions that slip right past you.
I imagine that, had I employed stackPane properties properly with the setStyle(java.lang.String) method, they would have absolutely shown up, and I was just butchering the nature of a stack pane. Interesting.
Hopefully this helps somebody else stuck with similar problems!