Does anyone know if there is a simple way to make images snap to grid when dragging a bitmap?
At the moment I can touch a bitmap and move it smoothly around the screen. I want to be able to make it snap to an invisible grid whilst you are dragging.
It's what I do in an application I'm just finishing at the moment. When the user is dragging something on the screen, I display a visible snap grid, and the object is snapped to that grid when the dragging has finished. To show a grid, my approach is to use a separate custom View that I named GridOverLayView. It is overlaid over the entire screen area and it very simply draws a snap grid in its onDraw() method. It is made visible only when something is being dragged.
Now, concerning the actual Activity in which dragging and dropping is handled, one particular constant I've defined is:
static final int SNAP_GRID_INTERVAL = 20;
When the object is being dragged around, i.e. when processing event.getAction()==MotionEvent.ACTION_MOVE events within my OnTouchListener, I perform snapping of the object's location to grid using the following:
RelativeLayout.LayoutParams par = (RelativeLayout.LayoutParams) mThingBeingDragged.getLayoutParams();
par.topMargin = Math.round((event.getRawY() - draggedInitialY) / SNAP_GRID_INTERVAL ) * SNAP_GRID_INTERVAL;
par.leftMargin = Math.round((event.getRawX() - draggedInitialX) / SNAP_GRID_INTERVAL ) * SNAP_GRID_INTERVAL;
mThingBeingDragged.setLayoutParams(par);
...where draggedInitialY and draggedInitialX store the initial touch position recorded during the initial MotionEvent.ACTION_DOWN.
A further nice touch is to allow the object being dragged to be moved around without snapping, but have it snap to the grid only in the .ACTION_UP when the user lifts their finger. In practice, this feels much nicer to use.
private PointF touchDown;
private int gridCellSize = 10;
private OnTouchListener touchListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
touchDown = new PointF(event.getRawX(), event.getRawY());
break;
}
case MotionEvent.ACTION_MOVE:
{
RelativeLayout.LayoutParams par = (RelativeLayout.LayoutParams) v.getLayoutParams();
float yDeff = ((event.getRawY() - touchDown.y) / gridCellSize ) * gridCellSize;
float xDeff = ((event.getRawX() - touchDown.x) / gridCellSize ) * gridCellSize;
if(Math.abs(xDeff) >= gridCellSize)
{
par.leftMargin += (int)(xDeff / gridCellSize) * gridCellSize;
touchDown.x = event.getRawX() - (xDeff % gridCellSize);
}
if(Math.abs(yDeff) >= gridCellSize)
{
par.topMargin += (int)(yDeff / gridCellSize) * gridCellSize;
touchDown.y = event.getRawY() - (yDeff % gridCellSize);
}
v.setLayoutParams(par);
break;
}
default :
{
break;
}
}
return true;
}
};
Related
I perform some drawing on a canvas every 10 sec. Unfortunately it disappears before it will be redrawn, so we have 10 sec with a blank screen. I tired to save a canvas before drawing and restore it, did not help.
This bug was appeared after I had shifted a line canvas.drawPath(linePath, linePaint); from outside of a loop into the loop.
CODE:
private void drawLine(Canvas canvas) {
yStep = (yHeight) / fullChargeLevel;
xStep = (xWidth) / timeStampBarsQuantity;
boolean check = false;
float time;
float chrg;
while (batteryUsageHistory != null && batteryUsageHistory.moveToNext()) {
int charge = batteryUsageHistory.getInt(1);
int time_stamp = batteryUsageHistory.getInt(2);
if (charge < 1) {
if(check){
canvas.drawPath(linePath, linePaint); //This line I shifted into here
linePath.reset();
}
check = false;
continue;
}
time = xPos + time_stamp * xStep;
chrg = yPos - (charge * yStep);
if (!check) {
linePath.moveTo(time, chrg);
check = true;
continue;
}
linePath.lineTo(time, chrg);
}
//canvas.drawPath(linePath, linePaint); //This line I shifted from here
}
You should implement your drawing logic in onDraw by extending some View class:
protected void onDraw(Canvas canvas) {
// here goes your custom drawing logic
}
as stated in: https://developer.android.com/training/custom-views/custom-drawing.html
it is because android redraws the component when it needs to. It is always like that you need to implement the drawing method and the GUI framework will call this method when neccessary, it is not the opposite, that the GUI framework displays your painting, it is just calling your painting method when needed.
I just moved back that line canvas.drawPath(linePath, linePaint); I was previously moved. And it works!.
I'm trying to implement face detection in my camera preview. I followed the Android reference pages to implement a custom camera preview in a TextureView, placed in a FrameLayout. Also in this FrameLayout is a SurfaceView with a clear background (overlapping the camera preview). My app draws the Rect that is recognized by the first CaptureResult.STATISTICS_FACES face's bounds dynamically to the SurfaceView's canvas, every time the camera preview is updated (once per frame). My app assumes only one face will need to be recognized.
My issue arises when I draw the rectangle. I get the rectangle in the correct place if I keep my face in the center of the camera view, but when I move my head upward the rectangle moves to the right, and when I move my head to the right, the rectangle moves down. It's as if the canvas needs to be rotated by -90, but this doesn't work for me (Explained below code).
in my activity's onCreate():
//face recognition
rectangleView = (SurfaceView) findViewById(R.id.rectangleView);
rectangleView.setZOrderOnTop(true);
rectangleView.getHolder().setFormat(
PixelFormat.TRANSPARENT); //remove black background from view
purplePaint = new Paint();
purplePaint.setColor(Color.rgb(175,0,255));
purplePaint.setStyle(Paint.Style.STROKE);
in TextureView.SurfaceTextureListener.onSurfaceTextureAvailable()(in the try{} block that encapsulates camera.open() :
Rect cameraBounds = cameraCharacteristics.get(
CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
cameraWidth = cameraBounds.right;
cameraHeight = cameraBounds.bottom;
in the same listener within onSurfaceTextureUpdated() :
if (detectedFace != null && rectangleFace.height() > 0) {
Canvas currentCanvas = rectangleView.getHolder().lockCanvas();
if (currentCanvas != null) {
currentCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
int canvasWidth = currentCanvas.getWidth();
int canvasHeight = currentCanvas.getHeight();
int l = rectangleFace.right;
int t = rectangleFace.bottom;
int r = rectangleFace.left;
int b = rectangleFace.top;
int left = (canvasWidth*l)/cameraWidth;
int top = (canvasHeight*t)/cameraHeight;
int right = (canvasWidth*r)/cameraWidth;
int bottom = (canvasHeight*b)/cameraHeight;
currentCanvas.drawRect(left, top, right, bottom, purplePaint);
}
rectangleView.getHolder().unlockCanvasAndPost(currentCanvas);
}
method onCaptureCompleted in CameraCaptureSession.CameraCallback called by CameraCaptureSession.setRepeatingRequest() looper:
//May need better face recognition sdk or api
Face[] faces = result.get(CaptureResult.STATISTICS_FACES);
if (faces.length > 0)
{
detectedFace = faces[0];
rectangleFace = detectedFace.getBounds();
}
All variables are instantiated outside of the functions.
In case you can't quite understand my question or need additional information, a similar question is posted here:
How can i handle the rotation issue with Preview & FaceDetection
However, unlike the above poster, I couldn't even get my canvas to show the rectangle after rotating my canvas, so that can't be the solution.
I tried to rotate my points by -90 degrees using x=-y, y=x (left=-top, top=left), and it doesn't do the trick either. I feel like some kind of function needs to be applied to the points but I don't know how to go about it.
Any ideas on how to fix this?
For future reference, this is the solution I ended up with:
set a class/Activity variable called orientation_offset :
orientation_offset = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
This is the angle that the camera sensor's view (or rectangle for face detection) needs to be rotated to be viewed correctly.
Then, I changed onSurfaceTextureUpdated() :
Canvas currentCanvas = rectangleView.getHolder().lockCanvas();
if (currentCanvas != null) {
currentCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
if (detectedFace != null && rectangleFace.height() > 0) {
int canvasWidth = currentCanvas.getWidth();
int canvasHeight = currentCanvas.getHeight();
int faceWidthOffset = rectangleFace.width()/8;
int faceHeightOffset = rectangleFace.height()/8;
currentCanvas.save();
currentCanvas.rotate(360 - orientation_offset, canvasWidth / 2,
canvasHeight / 2);
int l = rectangleFace.right;
int t = rectangleFace.bottom;
int r = rectangleFace.left;
int b = rectangleFace.top;
int left = (canvasWidth - (canvasWidth*l)/cameraWidth)-(faceWidthOffset);
int top = (canvasHeight*t)/cameraHeight - (faceHeightOffset);
int right = (canvasWidth - (canvasWidth*r)/cameraWidth) + (faceWidthOffset);
int bottom = (canvasHeight*b)/cameraHeight + (faceHeightOffset);
currentCanvas.drawRect(left, top, right, bottom, purplePaint);
currentCanvas.restore();
}
}
rectangleView.getHolder().unlockCanvasAndPost(currentCanvas);
I'll leave the question open in case somebody else has a solution to offer.
I am trying to draw an image that is larger than the screen, and let the user scroll around it. The image is some graphs that are calculated by my code from some data. (This is in Android using Java).
What I want is a View (actually a SurfaceView) that can be scrolled by the user. I have tried putting a SurfaceView inside a ScrollView but the whole image drawn by the SurfaceView is very large and so (a) there are performance issues as my app is drawing the whole SurfaceView when it should only need to do the part that is in the viewport and (b) my app crashes with "dimensions too large... out of memory" errors.
It would be best for me if the View could implement scrolling (vertical only) and then pass the y-value to my code so it could draw the visible part of my image in the viewport.
Alternatively I could capture the 'mouse' events and calculate the y-value myself.
Does anyone have any code so that I can do this?
Update: I should clarify, the image is drawn by my program code using Canvas.drawLine() and the like. I can calculate which parts of the image fit within my viewport, provided I have the state of the scroll as a y-value, and I can calculate the absolute y-coordinate of each point by subtracting scroll y-value. What I need is something that works to find when the user scrolls the image and what the resulting y-value is. A scrollbar would also be nice.
Update: I am using SurfaceView so that I can update the image from another Thread and so improve user interface performance.
if you really have just a draw content... you don't need a scrollable client then...
have a model (x,y,lines, etc)
have a pan & zoom
draw a scaled/zoomed instance of the model
just draw the content (sorry - no scroller in this solution)
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean isProcessed = scaleGestureDetector.onTouchEvent(event);
if (isProcessed) {
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
start.set(event.getX(), event.getY());
tap( event.getX(), event.getY() );
mode = DRAG_OR_TAP;
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG_OR_TAP) {
doPan(event.getX() - start.x, event.getY() - start.y);
start.set(event.getX(), event.getY());
}
break;
}
}
myView.scale(currentPan, currentScaleFactor);
invalidate();
return true;
}
private void doPan(float panX, float panY) {
currentPan.x = currentPan.x + panX;
currentPan.y = currentPan.y + panY;
}
private void tap(float x, float y) {
...
}
and last not least use the scaleListener
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
float value = detector.getScaleFactor();
currentScaleFactor = currentScaleFactor * value;
// don't let the object get too small or too large.
boolean doesntMatch = false;
if (currentScaleFactor < 1f || currentScaleFactor > 20f){
currentScaleFactor = Math.max(1f, Math.min(currentScaleFactor, 20f));
doesntMatch = true;
}
if(!doesntMatch){
//scale the viewport as well
currentPan.x = currentPan.x*value;
currentPan.y = currentPan.y*value;
}
return true;
}
}
you can simply use a WebView to display an image
provides pan
provides zoom
no library required
has a scrollbar
Android: Easiest way to make a WebView display a Bitmap?
i'm not sure if i really hit your question...
Occasionally I have to display a popup or dialog relative to an existing component (prime example is a date input control with a calendar button beside it).
It worked beautifully for years, but always had the bug that the calendar could partially appear outside the screen (it was hardcoded to appear just to the right of the field). Just nobody ever noticed because there was never a date control at the far right in a window. Well that changed recently with the addition of a new window.
Well then, I thought, lets just fix a windows position (after I positioned it where it should be) to be completely on screen. I wrote a simple utility method to do just that:
public static void correctWindowLocationForScreen(Window window) {
GraphicsConfiguration gc = window.getGraphicsConfiguration();
Rectangle screenRect = gc.getBounds();
Rectangle windowRect = window.getBounds();
Rectangle newRect = new Rectangle(windowRect);
if (windowRect.x + windowRect.width > screenRect.x + screenRect.width)
newRect.x = screenRect.x + screenRect.width - windowRect.width;
if (windowRect.y + windowRect.height > screenRect.y + screenRect.height)
newRect.y = screenRect.y + screenRect.height - windowRect.height;
if (windowRect.x < screenRect.x)
newRect.x = screenRect.x;
if (windowRect.y < screenRect.y)
newRect.y = screenRect.y;
if (!newRect.equals(windowRect))
window.setLocation(newRect.x, newRect.y);
}
Problem solved. Or not. I position my window using the on-screen coordinates from the triggering component (the button that makes the calendar appear):
JComponent invoker = ... // passed in from the date field (a JButton)
Window owner = SwingUtilities.getWindowAncestor(invoker);
JDialog dialog = new JDialog(owner);
dialog.setLocation(invoker.getLocationOnScreen());
correctWindowLocationForScreen(dialog);
Havoc breaks out if the "invoker" component is located in a window that spans two screens. Apparently "window.getGraphicsConfiguration()" returns whatever graphic configuration the windows top left corner happens to be in. Thats not necessarily the screen where the date component within the window is located.
So how can I position my dialog properly in this case?
One can iterate over all devices, and find the monitor where the point is in. Then keep to that Rectangle.
See GraphicsEnvironment.getScreenDevices.
This will not use the current Window, but you already found out that a window may be shown in several monitors.
Useful might be Component.getLocationOnScreen.
Ok, here is what I ended up with (a wall of code to handle the odd edge case).
correctWindowLocationForScreen() will reposition a window if it is not completely within the visible screen area (simplest case, its completely on one screen. Hard case, it spans multiple screens). If the window leaves the complete screen area by just one pixel, it is repositioned using the first screen rectangle found. If the window doesn't fit the screen, its positioned at the top left and extends over the screen to bottom right (its implied by the order in which positionInsideRectangle() checks/alters coordinates).
Its quite complicated considering the requirement is pretty simple.
/**
* Check that window is completely on screen, if not correct position.
* Will not ensure the window fits completely onto the screen.
*/
public static void correctWindowLocationForScreen(final Window window) {
correctComponentLocation(window, getScreenRectangles());
}
/**
* Set the component location so that it is completely inside the available
* regions (if possible).
* Although the method will make some effort to place the component
* nicely, it may end up partially outside the regions (either because it
* doesn't fit at all, or the regions are placed badly).
*/
public static void correctComponentLocation(final Component component, final Rectangle ... availableRegions) {
// check the simple cases (component completely inside one region, no regions available)
final Rectangle bounds = component.getBounds();
if (availableRegions == null || availableRegions.length <= 0)
return;
final List<Rectangle> intersecting = new ArrayList<>(3);
for (final Rectangle region : availableRegions) {
if (region.contains(bounds)) {
return;
} else if (region.intersects(bounds)) {
// partial overlap
intersecting.add(region);
}
}
switch (intersecting.size()) {
case 0:
// position component in the first available region
positionInsideRectangle(component, availableRegions[0]);
return;
case 1:
// position component in the only intersecting region
positionInsideRectangle(component, intersecting.get(0));
return;
default:
// uuuh oooh...
break;
}
// build area containing all detected intersections
// and check if the bounds fall completely into the intersection area
final Area area = new Area();
for (final Rectangle region : intersecting) {
final Rectangle2D r2d = new Rectangle2D.Double(region.x, region.y, region.width, region.height);
area.add(new Area(r2d));
}
final Rectangle2D boundsRect = new Rectangle2D.Double(bounds.x, bounds.y, bounds.width, bounds.height);
if (area.contains(boundsRect))
return;
// bah, just place it in the first intersecting region...
positionInsideRectangle(component, intersecting.get(0));
}
/**
* Position component so that its completely inside the rectangle.
* If the component is larger than the rectangle, component will
* exceed to rectangle bounds to the right and bottom, e.g.
* the component is placed at the rectangles x respectively y.
*/
public static void positionInsideRectangle(final Component component, final Rectangle region) {
final Rectangle bounds = component.getBounds();
int x = bounds.x;
int y = bounds.y;
if (x + bounds.width > region.x + region.width)
x = region.x + region.width - bounds.width;
if (y + bounds.height > region.y + region.height)
y = region.y + region.height - bounds.height;
if (region.x < region.x)
x = region.x;
if (y < region.y)
y = region.y;
if (x != bounds.x || y != bounds.y)
component.setLocation(x, y);
}
/**
* Gets the available display space as an arrays of rectangles
* (there is one rectangle for each screen, if the environment is
* headless the resulting array will be empty).
*/
public static Rectangle[] getScreenRectangles() {
try {
Rectangle[] result;
final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice[] devices = ge.getScreenDevices();
result = new Rectangle[devices.length];
for (int i=0; i<devices.length; ++i) {
final GraphicsDevice gd = devices[i];
result[i] = gd.getDefaultConfiguration().getBounds();
}
return result;
} catch (final Exception e) {
return new Rectangle[0];
}
}
I am working with images only and the dimensions of the window that I am using to view my application may be different on different systems. I have a mouse action listener that is listening for clicks on the main view of my program. I have a rounded rectangle that looks like a button. I want to make it so that way the mouse action listener only listens to the area of the rounded rectangle rather than the entire image on all systems. Like the title says, not the entire image has content, in particular, the corners don't look like they are part of the image, so I don't want to allow the user to be able to click on parts of the image without content and get the same result as if they clicked on the part with content.
My image looks similar to this
(source: youthedesigner.com)
So I only want the program to do something if the user clicks on the button inside the image rather than the nice stuff around the button.
This is what I have right now to listen to clicks:
#Override
public void mouseClicked(MouseEvent e) {
for(int i = 0; i <= 200; i++) {
if(e.getY() >= 100+i && e.getY() <= 300) {
if(e.getX() >= 10+100-Math.pow(10000-(Math.pow((i-100),2.0)),.5)) && e.getX() <= 10+400-Math.pow(10000-(Math.pow((i-100),2.0)),.5))) {
// do stuff
i = 201;
}
}
}
}
The math equation I am using in my code looks like 110-(10000-(y-100)^2)^(1/2)), which, if graphed, would look like an open parenthesis, and 410+(10000-(y-100)^2)^(1/2)), which would look like a close parenthesis 400 units away from the first graph.
The code works fine on my system, but on other systems, it doesn't work at all and I am curious how I could move the location I am listening to to correspond to how the image is scaled.
Thank you very much for any help you can provide.
The for-loop is superfluous.
You could ensure that pixels outside the button (.png) have some transparency, and then check for the alpha color component.
In this case you could add a Rect and look for that:
private boolean insideButton(Rectangle buttonRect, Point mousePt) {
if (buttonRect.contains(mousePt)) {
int r = buttonRect.height() / 2;
if (mousePt.x < r) {
// Left circle with O at (r, r)
int xFromO = r - mousePt.x;
int yFromO = r - mousePt.y;
if (xFromO * xFromO + yFromO * yFromO > r * r) {
return false; // Outside circle
}
}
if (mousePt.x > buttonRect.right - r) {
// Right circle:
...
}
return true;
}
return false;
}
So, I used Joop's answer to solve my problem. His answer wasn't quite what I was looking for, but it gave me the idea I needed to solve my problem. The solution I came to was:
private boolean insideButton(Rectangle buttonRect, Point mousePt) {
if (buttonRect.contains(mousePt)) {
int r = (int)buttonRect.getHeight()/2; // radius of either of the circles that make up the sides of the rectangle
if(mousePt.x <= buttonRect.getWidth()/2) { // if it is on the left of the button
Point center = new Point((int)buttonRect.getX()+r, (int)buttonRect.getY()+r); // the center of the circle on the left
double lengthToPoint = Math.pow(Math.pow(mousePt.x-center.x, 2)+Math.pow(mousePt.y-center.y, 2), 1.0/2); // length from center to the point that the user clicked at
if(lengthToPoint > r && mousePt.x < center.x) { // if it is to the left of the center and out of the circle
return false;
} else {
return true;
}
} else { // if it is on the right, the rest of the code is just about the same as the left circle
Point center = new Point((int)buttonRect.getWidth()-r, (int)buttonRect.getY()+r);
double lengthToPoint = Math.pow(Math.pow(mousePt.x-center.x, 2)+Math.pow(mousePt.y-center.y, 2), 1.0/2);
if(lengthToPoint > r && mousePt.x > center.x) {
return false;
} else {
return true;
}
}
} else {
return false;
}
}
I know it is goes a little overboard with calculations and inefficient, but I wanted to present it this way to show a better idea of how my solution works.
I can think of at least two ways.
The first is to produce a mask image (black and white), where (for example) white would indicate the clickable area. Basically, you could compare the pixel color of the mask based in click pick point of the original image.
The other way would be to build a image map, basically using something like a Shape API to allow for non-rectangular shapes. This would allow to use Shape#contains to determine if the mouse clicked inside it or not
In either case, you need to take into account the x/y position of the original image