I just need to set a high res image, but wants to know if its possible to set it in xml. I want to be able to set it even if it will be bigger than the mobile screen.
EDIT: I want to add a huge image, so the user will have to scroll it. Something like a map, but need to add clickable areas on it and I only know how to do this in xml. That's why I asked to add the image using xml not in java. But if someone can tell me how to add clickable areas in java. That will do it better, since I already have the image but using java and its able to scroll.
EDIT: Heres where I got the code from : Anddev.org I'm using exactly the same code, just with other image.
What are you trying for? In android the resolutions are based on like below -
Screen Support
MultipleResolution
Just read out this. Hope these two links enough for your query.
Have a look at this sample code to see how to scroll an image larger than the screen. It also does bitmap caching to speed up drawing of a complex image, and shows how to respond to long-taps and double-taps at any point on the image.
Ok I'm posting this as an answer, just because it will be easier to post this. (It's not working how I want it to, but I got something) I want to share this so anyone else, who knows how to, can help me out here to find a solution for this one. This is the code
package com.example.largeimagescroller;
import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.Toast;
public class LargeImageScroller extends Activity {
// Physical display width and height.
private static int displayWidth = 0;
private static int displayHeight = 0;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// displayWidth and displayHeight will change depending on screen
// orientation. To get these dynamically, we should hook
// onSizeChanged().
// This simple example uses only landscape mode, so it's ok to get them
// once on startup and use those values throughout.
Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
displayWidth = display.getWidth();
displayHeight = display.getHeight();
// SampleView constructor must be constructed last as it needs the
// displayWidth and displayHeight we just got.
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private static Bitmap bmLargeImage; // bitmap large enough to be
// scrolled
private static Rect displayRect = null; // rect we display to
private Rect scrollRect = null; // rect we scroll over our bitmap with
private int scrollRectX = 0; // current left location of scroll rect
private int scrollRectY = 0; // current top location of scroll rect
private float scrollByX = 0; // x amount to scroll by
private float scrollByY = 0; // y amount to scroll by
private float startX = 0; // track x from one ACTION_MOVE to the next
private float startY = 0; // track y from one ACTION_MOVE to the next
public SampleView(Context context) {
super(context);
// Destination rect for our main canvas draw. It never changes.
displayRect = new Rect(0, 0, displayWidth, displayHeight);
// Scroll rect: this will be used to 'scroll around' over the
// bitmap in memory. Initialize as above.
scrollRect = new Rect(0, 0, displayWidth, displayHeight);
// Load a large bitmap into an offscreen area of memory.
bmLargeImage = BitmapFactory.decodeResource(getResources(),
R.drawable.alienware);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
//This code define what to do if you touch the x and y coordinates.
float x1 = event.getX();
float y1 = event.getY();
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
if (x1>150 & x1<200 & y1>400 & y1<500){
Toast.makeText(getContext(), "Touched the coordinates.", Toast.LENGTH_SHORT).show();
}
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Remember our initial down event location.
startX = event.getRawX();
startY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float x = event.getRawX();
float y = event.getRawY();
// Calculate move update. This will happen many times
// during the course of a single movement gesture.
scrollByX = x - startX; // move update x increment
scrollByY = y - startY; // move update y increment
startX = x; // reset initial values to latest
startY = y;
invalidate(); // force a redraw
break;
}
return true; // done with this event so consume it
}
#Override
protected void onDraw(Canvas canvas) {
// Our move updates are calculated in ACTION_MOVE in the opposite
// direction
// from how we want to move the scroll rect. Think of this as
// dragging to
// the left being the same as sliding the scroll rect to the right.
int newScrollRectX = scrollRectX - (int) scrollByX;
int newScrollRectY = scrollRectY - (int) scrollByY;
// Don't scroll off the left or right edges of the bitmap.
if (newScrollRectX < 0)
newScrollRectX = 0;
else if (newScrollRectX > (bmLargeImage.getWidth() - displayWidth))
newScrollRectX = (bmLargeImage.getWidth() - displayWidth);
// Don't scroll off the top or bottom edges of the bitmap.
if (newScrollRectY < 0)
newScrollRectY = 0;
else if (newScrollRectY > (bmLargeImage.getHeight() - displayHeight))
newScrollRectY = (bmLargeImage.getHeight() - displayHeight);
// We have our updated scroll rect coordinates, set them and draw.
scrollRect.set(newScrollRectX, newScrollRectY, newScrollRectX
+ displayWidth, newScrollRectY + displayHeight);
Paint paint = new Paint();
canvas.drawBitmap(bmLargeImage, scrollRect, displayRect, paint);
// Reset current scroll coordinates to reflect the latest updates,
// so we can repeat this update process.
scrollRectX = newScrollRectX;
scrollRectY = newScrollRectY;
}
}
}
I rec the application in use. (Since touch events don't work with cpu emulators I did it on my phone)
http://www.youtube.com/watch?v=NrszZoDenXE&feature=youtube_gdata
As you can see its taking the touch, but it moves with the scroll rect, so the question will be: How can I do the touch to stay in the image, so it doesn't move with the scrolling rect?
Thanks
Related
Based on my previous question of "How to create a BottomBar as StickyBottomCaptureLayout in camera2 Android api?", I created a layout with a StickyBar (SB) which is always locked above/near the system bar. I set the default positions and coordinates of the SB and the other layout in onLayout() (exactly as my answer).
The upper layout is a simple custom DrawView which has an ArrayList of Paths drew by the user. When the device rotates, it recalls onDraw() and calls several times canvas.drawPath(). However, the Paths are redrew with the same coordinates as before but on a different position and layout size. These screenshots demonstrate the actual behavior:
left: portrait - right: landscape
But I want to keep the same coordinates and positions when the orientation changed, like this:
left: same portrait as above - right: landscape with "portrait" coordinates
Locking my activity with android:orientation="portrait" is not the expected solution. I use android:configChanges="orientation" and an OrientationListener to detect the rotation and prevent the total recreation of the Activity.
I tried to set other different positions in onLayout() but obviously, this is not the right way.
I previously tried to transform the multiple Paths like this:
for (Path path : mPathList) {
Matrix matrix = new Matrix();
RectF bounds = new RectF();
path.computeBounds(bounds, true);
// center points to rotate
final float px = bounds.centerX();
final float py = bounds.centerY();
// distance points to move
final float dx; // ?
final float dy; // ?
/** I tried many calculations without success, it's
not worth to paste these dumb calculations here... **/
matrix.postRotate(rotation, px, py); // rotation is 90°, -90° or 0
matrix.postTranslate(dx, dy); // ?
path.transform(matrix);
}
I also tried to rotate the canvas as follows:
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.rotate(rotation); // rotation is 90°, -90° or 0
canvas.drawColor(mDrawHelper.getBackgroundViewColor());
for (int i=0; i < mPathList.size(); i++) {
canvas.drawPath(mPathList.get(i), mPaintList.get(i));
}
if (mPath != null && mPaint != null)
canvas.drawPath(mPath, mPaint);
canvas.restore();
}
Anyway, I tried many manipulations but nothing seems to work in this specific case. Does someone have a bright and fabulous idea to share which can lead me in the right direction?
Thanks in advance for the help.
Update: Methodology has been simplified and made easier to follow. The sample app has been updated.
I think I understand what you are trying to do. You want the graphic to maintain its relationship with the StickyCaptureLayout that you have defined. I like the approach using Path and Matrix transformations.
After determining the rotation that the device has undergone, create a Matrix to do the appropriate rotation and rotate about the center of the graphic.
mMatrix.postRotate(rotationDegrees, oldBounds.centerX(), oldBounds.centerY());
Here oldBounds is the bounds of the graphic before location. We will use this to determine margins on the rotated graphic. Go ahead and do the rotation
mPath.transform(mMatrix)
The graphic has been rotated but its position is not correct. It is in the old position but rotated. Create a translation Matrix to move the Path to the appropriate location. The actual computation is dependent upon the rotation. For a 90 degree rotation the computation is
transY = -newBounds.bottom; // move bottom of graphic to top of View
transY += getHeight(); // move to bottom of rotated view
transY -= (getHeight() - oldBounds.right); // finally move up by old right margin
transX = -newBounds.left; // Pull graphic to left of container
transX += getWidth() - oldBounds.bottom; // and pull right for margin
where transY is the Y-translation and transX is the X-translation. oldBounds is the pre-rotation bounds and newBounds is the post-rotation bounds. Important to note here is that getWidth() will give you the "old" View height and getHeight() will give you the old View width.
Here is a sample program that accomplishes what I have described above. A couple of graphics follow showing a 90 degree rotation using this sample app.
Demo app
package com.example.rotatetranslatedemo;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.Display;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
public class MainActivity extends Activity {
private DrawingView dv;
private Paint mPaint;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dv = new DrawingView(this);
setContentView(dv);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.GREEN);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(12);
}
public class DrawingView extends View {
private Bitmap mBitmap;
private Path mPath;
private Paint mBitmapPaint;
Context context;
private Paint paint;
Matrix mMatrix = new Matrix();
RectF oldBounds = new RectF();
RectF newBounds = new RectF();
public DrawingView(Context c) {
super(c);
context = c;
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.MITER);
paint.setStrokeWidth(4f);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
}
#Override
protected void onDraw(Canvas canvas) {
Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
int rotationDegrees = 0;
float transX = 0;
float transY = 0;
super.onDraw(canvas);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
// Determine the rotation of the screen.
switch (display.getRotation()) {
case Surface.ROTATION_0:
break;
case Surface.ROTATION_90:
rotationDegrees = 270;
break;
case Surface.ROTATION_180:
rotationDegrees = 180;
break;
case Surface.ROTATION_270:
rotationDegrees = 90;
break;
default:
rotationDegrees = 0;
break;
}
if (mPath == null) { // Just define what we are drawing/moving
mPath = setupGraphic();
}
// Reposition the graphic taking into account the current rotation.
if (rotationDegrees != 0) {
mMatrix.reset();
// Rotate the graphic by its center and in place.
mPath.computeBounds(oldBounds, true);
mMatrix.postRotate(rotationDegrees, oldBounds.centerX(), oldBounds.centerY());
mPath.transform(mMatrix);
// Get the bounds of the rotated graphic
mPath.computeBounds(newBounds, true);
mMatrix.reset();
if (rotationDegrees == 90) {
transY = -newBounds.bottom; // move bottom of graphic to top of View
transY += getHeight(); // move to bottom of rotated view
transY -= (getHeight() - oldBounds.right); // finally move up by old right margin
transX = -newBounds.left; // Pull graphic to left of container
transX += getWidth() - oldBounds.bottom; // and pull right for margin
} else if (rotationDegrees == 270) {
transY = -newBounds.top; // pull top of graphic to the top of View
transY += getHeight() - oldBounds.right; // move down for old right margin
transX = getWidth() - newBounds.right; // Pull to right side of View
transX -= getHeight() - oldBounds.right; // Reestablish right margin
}
mMatrix.postTranslate(transX, transY);
mPath.transform(mMatrix);
}
canvas.drawPath(mPath, mPaint);
}
// Define the graphix that we will draw and move.
private Path setupGraphic() {
int startX;
int startY;
final int border = 20;
Path path;
if (getHeight() > getWidth()) {
startX = getWidth() - border - 1;
startY = getHeight() - border - 1;
} else {
startX = getHeight() - border - 1;
startY = getWidth() - border - 1;
}
startX = startX - 200;
Pt[] myLines = {
new Pt(startX, startY),
new Pt(startX, startY - 500),
new Pt(startX, startY),
new Pt(startX - 100, startY),
new Pt(startX, startY - 500),
new Pt(startX - 50, startY - 400),
new Pt(startX, startY - 500),
new Pt(startX + 50, startY - 400),
new Pt(startX + 200, startY),
new Pt(startX + 200, startY - 500)
};
// Create the final Path
path = new Path();
for (int i = 0; i < myLines.length; i = i + 2) {
path.moveTo(myLines[i].x, myLines[i].y);
path.lineTo(myLines[i + 1].x, myLines[i + 1].y);
}
return path;
}
private static final String TAG = "DrawingView";
}
// Class to hold ordered pair
private class Pt {
float x, y;
Pt(float _x, float _y) {
x = _x;
y = _y;
}
}
}
Portrait
Landscape
Your solution #2 is almost correct. All you need to do is translate your canvas appropriately.
Assuming that rotation is declared as int and may be only 90, -90 or 0, you need to replace this line:
canvas.rotate(rotation); // rotation is 90°, -90° or 0
by the following code:
if (rotation == 90) {
canvas.translate(canvas.getWidth(), 0);
canvas.rotate(90);
} else if (rotation == -90) {
canvas.translate(0, canvas.getHeight());
canvas.rotate(-90);
}
This will work. I can set up a demo project if needed.
Instead of implementing a solution that is very specific to your problem, you can just implement a more generic one. For example a layout that will rotate everything what is inside, which in my opinion is much more elegant.
public class RotatedLayout extends FrameLayout {
public RotatedLayout(Context context) {
super(context);
}
...
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int rotation = 0;
boolean swapDimensions = false;
int translationX = 0;
int translationY = 0;
final Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
switch (display.getRotation()) {
case Surface.ROTATION_0:
rotation = 0;
break;
case Surface.ROTATION_90:
rotation = -90;
swapDimensions = true;
break;
case Surface.ROTATION_180:
rotation = 180;
break;
case Surface.ROTATION_270:
rotation = 90;
swapDimensions = true;
break;
}
if (swapDimensions) {
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
translationX = (width - height) / 2;
translationY = (height - width) / 2;
final int tmpMeasureSpec = heightMeasureSpec;
heightMeasureSpec = widthMeasureSpec;
widthMeasureSpec = tmpMeasureSpec;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setTranslationX(translationX);
setTranslationY(translationY);
setRotation(rotation);
}
}
This layout is rather straightforward. It forces itself to be measured with swapped dimensions if displayed in landscape mode. It doesn't care what is inside, so you can put everything there, also a regular interface. After measuring itself (and children) with swapped MeasureSpecs it rotates itself and translates, using view properties, to fit the new position. As a bonus of using view properties - touch events works just fine and this button can be pressed as usual.
In portrait orientation:
Rotated to the left:
Problem with onConfigurationChanged
Although this layout will always draw itself in correct orientation, there must be some event that will cause it to be re-drawn. This may be a problem if you rely only on onConfigurationChanged. In your case Activity can react on changes from landscape to portrait and portrait to landscape. But there is no event sent when switching directly from:
portrait orientation to reverted portrait (if you have the reversed portrait enabled in your AndroidManifest) - marked in blue.
landscape orientation to reversed landscape - marked in red
Please keep in mind that such direct orientation swapping to the reversed orientation is a normal interaction on regular device, it is not something artificial that you can do on emulator only.
There is no standard event sent that will cause views to redraw themselves - no onConfigurationChanged, onMeasure, onLayout, onDraw etc. is invoked.
System just rotates everything for you (without even redrawing it) and it will result in wrong rotation of the view RotatedLayout had no changes to correct it. So be aware that you have to handle this case.
You can see it here in an answered by Dianne Hackborn.
This is simply not a configuration change. There is no notification the
platform provides for when it does this, because it is invisible to the
environment the app is in.
To solve this problem you would have to use SensorManager and register OrientationEventListener to determine when to refresh your view instead of relying on onConfigurationChanged method.
I am doing a Pong clone and I've implemented the ball as a Rect() object. Now to move the ball I use the Rect.offset(dx,dy), which offsets the ball by a specefied speed. For bouncing the ball off a wall I multiply the velocity of respective axis by -1. Now for bounce along the Y-axis , it bounces perfectly, but along the X-axis it starts behavioral weirdly. Is this some glitch with the android studio or am I doing something wrong?
package com.nblsoft.pong;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
public class PongLogic extends View {
//set screen constrains in dip
Configuration configuration = this.getResources().getConfiguration();
int dpHeight = configuration.screenHeightDp; //The current height of the available screen space, in dp units, corresponding to screen height resource qualifier.
int dpWidth = configuration.screenWidthDp; //The current width of the available screen space, in dp units, corresponding to screen width resource qualifier.
//int smallestScreenWidthDp = configuration.smallestScreenWidthDp; //The smallest screen size an application will see in normal operation, corresponding to smallest screen width resource qualifier.
//DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics();
//float dpHeight = displayMetrics.heightPixels / displayMetrics.density;
//float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
private int dptopixel(int DESIRED_DP_VALUE){
final float scale = getResources().getDisplayMetrics().density;
return (int)((DESIRED_DP_VALUE) * scale + 0.5f);
}
private int pixeltodp(int DESIRED_PIXEL_VALUE){
final float scale = getResources().getDisplayMetrics().density;
return (int) ((DESIRED_PIXEL_VALUE) - 0.5f / scale);
}
//set paddle size, speed, position vector
int AI_paddle_pos_x = 4 * (dptopixel(dpWidth)/100); //3 for 320x480, 10 for 1080x1920 etc.
int paddle_width = (dptopixel(dpWidth)/10); //
int AI_paddle_pos_y = (dptopixel(dpHeight)/10); //48 for 320x480, 190 for 1080x1920 etc.
int paddle_height = (dptopixel(dpHeight)/100) + 3; //the paddle is 100% of the total height of phone.
int user_paddle_pos_x = 4 * (dptopixel(dpWidth)/100) ;
int user_paddle_pos_y = dptopixel(dpHeight) - ((dptopixel(dpHeight)/10) + (dptopixel(dpHeight)/100) + 3) ;
//User Paddle
public Rect paddle_user = new Rect(user_paddle_pos_x,
user_paddle_pos_y,
user_paddle_pos_x + paddle_width,
user_paddle_pos_y + paddle_height);
//AI paddle
Rect paddle_AI = new Rect(AI_paddle_pos_x,
AI_paddle_pos_y,
AI_paddle_pos_x + paddle_width,
AI_paddle_pos_y + paddle_height);
//set ball position vector, Velocity vector, acceleration
int ball_pos_x = 0 ;
int ball_pos_y = (dptopixel(dpHeight)/2) ;
int ball_size = dptopixel(dpWidth)/100 ;
int ball_velocity = 3;
// Ball
Rect ball = new Rect(ball_pos_x,
ball_pos_y,
ball_pos_x+ball_size,
ball_pos_y+ball_size);
//Override onDraw method
#Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
Paint mytext = new Paint();
mytext.setColor(Color.WHITE);
//mytext.setStyle(Paint.Style.STROKE);
//mytext.setStrokeWidth(2);
// Draw Middle point
canvas.drawRect(0, ((dptopixel(dpHeight)) / 2), (dptopixel(dpWidth)), (((dptopixel(dpHeight)) / 2) + 2), mytext);
//draw both paddles
canvas.drawRect(paddle_user,mytext);
canvas.drawRect(paddle_AI, mytext);
//draw ball
canvas.drawRect(ball,mytext);
//Practise Methods
//canvas.drawText(Integer.toString(dptopixel(dpHeight)),300,300,mytext);
//canvas.drawText(Integer.toString(dptopixel(dpWidth)), 400, 400, mytext);
//canvas.drawText(Integer.toString(dpHeight),500,500,mytext);
//canvas.drawText(Integer.toString(dpWidth),600,600,mytext);
//canvas.drawText("Fuck", 700, 700, mytext);
//canvas.drawRect(0,0,dptopixel(dpWidth),dptopixel(dpHeight),mytext);
//Game Loop Updater
update();
invalidate();
}
private void update() {
if (ball.centerX() > (dptopixel(dpWidth))/2){
ball.offset(-ball_velocity,ball_velocity);
}
else{
ball.offset(ball_velocity,ball_velocity);
}
}
//Override Touch method
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
paddle_user.offset(10,0);
}
return true;
}
/* #Override
public boolean onTouch(View v, MotionEvent event) {
this.paddle_user.offsetTo(10,10);
return true; //Event Handled
}
*/
public PongLogic(Context context) {
super(context);
setBackgroundColor(Color.BLACK); //to set background
this.setFocusableInTouchMode(true); //to enable touch mode
}
}
Ok I've Figured out why the offset method was behaving erratically. First I had the original offset position in the else part of the conditional. Second I was negating the dx component only when the if evaluated to true. Which happened for just one frame because I was evaluating Rect object's X position. Which cause it to become false as soon as the -dx was subtracted from the ball's position.
To resolve it I had to place the offset out of the conditional and pass two separate values for the dx and dy. Then I tweaked the conditional so that it only assigned positive and negative values to the variables which were later passed into offset() function, Rather then calling the offset function itself.
This is the new update() method
private void update() {
if (ball.centerX() > (dptopixel(dpWidth))/2){
ball_velocity_x = -3;
}
else if (ball.centerY() > (dptopixel(dpHeight))) {
ball_velocity_y = -3;
}
ball.offset(ball_velocity_x, ball_velocity_y);
}
Ok, I'm trying to make a program that utilizes a linedrawview. When the user starts a touch event(action DOWN), it gets the current x and y and stores them in variables. Then when the user drags their finger around, a line is drawn and animates in a rubber band way. Finally, when the user lets go (action UP), the line is created. I'm having a lot of trouble with this and would like some assistance. My code so far for LineDrawView.java:
// Project: Java2LineDrawEx
// File: LineDrawView.java
// Date: 4/9/13
// Author: Joshua Lefelhocz
// Description: custom view to draw lines on
package com.lcc.java2lab11lefelhocz;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
// Notice this class extends View
public class LineDrawView extends View
{
// This view's bounds
private int xMin = 0;
private int xMax;
private int yMin = 0;
private int yMax;
private float currentX;
private float currentY;
// Paint object
private Paint paintFill;
// constructor
public LineDrawView(Context context)
{
// call the super class constructor
super(context);
// The Paint class holds the style and color information about how to draw geometries, text and bitmaps.
// For efficiency create the paint objects in the constructor, not in draw
// paint.setStrokeWidth(10); // works on lines
// You can change the color of Paint without effecting objects already drawn
// You can NOT change the style of Paint without effecting objects already drawn
// The Style, TextSize apply to all objects drawn with the paint.
// Create a default Paint object Style=Fill
paintFill = new Paint();
// set the background color when the view is created
this.setBackgroundColor(Color.LTGRAY);
}
// Called to draw the view. Also called by invalidate().
#Override
protected void onDraw(Canvas canvas)
{
// canvas.drawColor(Color.LTGRAY); // this works in onDraw to set the background color
// Draw a Red Diagonal line from upper left corner to bottom right corner
paintFill.setColor(Color.RED);
canvas.drawLine(xMin, yMin, xMax, yMax, paintFill);
// draw a blue line 10 pixels wide horizontal across the center.
paintFill.setColor(Color.BLUE);
paintFill.setStrokeWidth(10);
canvas.drawLine(xMin, yMax/2, xMax, yMax/2, paintFill);
// draw a yellow line 20 pixels wide vertical across the center.
paintFill.setColor(Color.YELLOW);
paintFill.setStrokeWidth(20);
canvas.drawLine(xMax/2, yMin, xMax/2, yMax, paintFill);
}
// Called when the view is first created or its size changes.
#Override
public void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
// Set the view bounds
xMax = width-1;
yMax = height-1;
}
public boolean onTouchEvent(MotionEvent event)
{
currentX = event.getX();
currentY = event.getY();
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
float startX = currentX;
float startY = currentY;
case MotionEvent.ACTION_MOVE:
float endX =
case MotionEvent.ACTION_UP:
return super.onTouchEvent(event);
}
return super.onTouchEvent(event);
}
}
Okay, I found out: Make sure to set the start position x and y, like this:
currentXExample = event.getX();
currentYExample = event.getY();
exampleStartX = currentXExample;
exampleStartY = currentYExample;
exampleEndX = currentXExample;
exampleEndY = currentYExample;
You can leave it out of the Up event, but never leave it out of the MOVE event or it will not work.
LineDrawView.java
// Project: Java2Lab
// File: LineDrawView.java
// Date: 4/9/13
// Author: Joshua Lefelhocz
// Description: custom view to draw lines on
package com.lcc.java2lab11lefelhocz;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
// Notice this class extends View
public class LineDrawView extends View
{
// This view's bounds
private int xMin = 0;
private int xMax;
private int yMin = 0;
private int yMax;
private float currentX;
private float currentY;
private float startX;
private float endX;
private float startY;
private float endY;
// Paint object
private Paint paintFill;
// constructor
public LineDrawView(Context context)
{
// call the super class constructor
super(context);
// The Paint class holds the style and color information about how to draw geometries, text and bitmaps.
// For efficiency create the paint objects in the constructor, not in draw
// paint.setStrokeWidth(10); // works on lines
// You can change the color of Paint without effecting objects already drawn
// You can NOT change the style of Paint without effecting objects already drawn
// The Style, TextSize apply to all objects drawn with the paint.
// Create a default Paint object Style=Fill
paintFill = new Paint();
// set the background color when the view is created
this.setBackgroundColor(Color.LTGRAY);
}
// Called to draw the view. Also called by invalidate().
#Override
protected void onDraw(Canvas canvas)
{
//canvas.drawColor(Color.LTGRAY); // this works in onDraw to set the background color
// Draw a Red Diagonal line from upper left corner to bottom right corner
paintFill.setColor(Color.BLACK);
canvas.drawLine(startX, startY, endX, endY, paintFill);
}
// Called when the view is first created or its size changes.
#Override
public void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
// Set the view bounds
xMax = width-1;
yMax = height-1;
}
public boolean onTouchEvent(MotionEvent event)
{
currentX = event.getX();
currentY = event.getY();
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
startX = currentX;
startY = currentY;
return true;
case MotionEvent.ACTION_MOVE:
endX = currentX;
endY = currentY;
invalidate();
return true;
case MotionEvent.ACTION_UP:
return true;
}
return super.onTouchEvent(event);
}
}
I have made the custom ImageView which can zoom and pan. The problem is with the panning/dragging bound I have tried to cap the bound by giving limit to the left top edge if it goes below Zero or negative. When I am doing this it works till one of the condition is occured like X becomes 0 or Y becomes 0 if both becomes 0 it gets jerk and some weird behaviour happens even if the less than zero condition is there it goes out of bound ..
code is with the comments easy to understand please help
here is the code
package com.example.customView;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.Camera.PreviewCallback;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
/* #description : Custom View Zoom
*
*/
public class ZoomView extends ImageView {
// Maximum and Minimum Zoom
private static float MIN_ZOOM = 1.0f;
private static float MAX_ZOOM = 3.0f;
//Different Operation to be used
private final int NONE_OPERATION=0;
private final int DRAG_OPERATION=1;
private final int ZOOM_OPERATION=2;
private float mWidth= 1047;
private float mHeight=800;
private boolean dragged=true;
// Mode to select the operation
private int mode;
//Track X and Y coordinate of the finger when it first touches the screen
private float mInitialX = 0f;
private float mInitialY = 0f;
//Track the amount to translate(Drag) the canvas along the X and the Y coordinate
private float mTranslateX = 0f;
private float mTranslateY = 0f;
//Track the last translated X and the Y coordinate while panning so that canvas does not get the jerk (Issue was happening when we change the position again and again )
private float mPreviousTranslateX = 0f;
private float mPreviousTranslateY = 0f;
//ScalingFactor i.e. Amount of Zoom
private float mScaleFactor = 1.0f;
float gx=0,gy=0;
private Rect rect ;
private Matrix matrix;
private ScaleGestureDetector mDetector;
// Called if used from code
public ZoomView(Context context) {
super(context);
// Intialize ScaleGestureDetector
mDetector = new ScaleGestureDetector(context, new ZoomListener());
rect= new Rect();
Matrix matrix= new Matrix();
}
//Called if used from XML
public ZoomView(Context context, AttributeSet attrs) {
super(context, attrs);
mDetector = new ScaleGestureDetector(context, new ZoomListener());
rect= new Rect();
Matrix matrix= new Matrix();
}
//handle the touch event of the view with the detector to get the scalingFactor and also keep the track of
// the touch events like drag and zoom event using booleans
#Override
public boolean onTouchEvent(MotionEvent event) {
// Handles all type of motion-events possible
switch(event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// Event occurs when the first finger is pressed on the Screen
// setting the mode to Drag Operation
mode = DRAG_OPERATION;
// Store the initial X and Y of the first finger when touches on the Screen. Take the difference with the previous translation so as to avoid the jerk in canvas.
//Initial difference will be X and Y since previousTranslation will be ZERO.
mInitialX = event.getX() - mPreviousTranslateX;
mInitialY = event.getY() - mPreviousTranslateY;
break;
case MotionEvent.ACTION_MOVE:
// Event occurs when the finger move across the screen and also when the finger is kept pressed on the screen
// Update the translate value constantly as the event is occured at every move
mTranslateX = event.getX() - mInitialX; // Translate value is calculated by diff from current and initial
mTranslateY = event.getY() - mInitialY;
/* float [] matrixValues = new float[9];
matrix.getValues(matrixValues);
if (mode == DRAG_OPERATION) {
if(rect.left<0 || matrixValues[Matrix.MTRANS_X]<0)
mTranslateX = mPreviousTranslateX;
else
mTranslateX = event.getX() - mInitialX; // Translate value is calculated by diff from current and initial
if(rect.top<0 || matrixValues[Matrix.MTRANS_Y]<0 )
mTranslateY=mPreviousTranslateY;
else
mTranslateY = event.getY() - mInitialY;
}*/
// Log.d("Print", " TranslateX::" + mTranslateX + " Translate Y::" + mTranslateY);
// If finger is kept pressed it will still consider the move so to avoid that use this value
//Initial X and Initial Y can not be used directly because they were adjusted using the previous translation values. So need to add those
// values to InitialX and InitialY so that the actual coordinates of the finger are retrieved.
// Using distance Forumla
double distance = Math.sqrt(Math.pow(event.getX() - (mInitialX + mPreviousTranslateX), 2) + Math.pow(event.getY() - (mInitialY + mPreviousTranslateY), 2));
if(distance > 0) {
dragged = true;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
//Event occurs when the second finger is pressed down
// If second finger is pressed on the screen with the first set the Mode to Zoom operation
mode=ZOOM_OPERATION;
break;
case MotionEvent.ACTION_UP:
//Event occurs when all the finger are taken of the screen
//If all the fingers are taken up there will be no operation
mode = NONE_OPERATION;
dragged= false;
// All the operations are done.Store the previousTranslate value here. ( Might not need at the time of second finger down ??)
mPreviousTranslateX = mTranslateX;
mPreviousTranslateY = mTranslateY;
break;
case MotionEvent.ACTION_POINTER_UP:
// Event occurs when the second finger is taken of the screen while first finger is pressed
// Second finger is taken up stop zooming and again Drag Operation
mode=DRAG_OPERATION;
break;
}
// give the event to the mDetector to get the scaling Factor
mDetector.onTouchEvent(event);
//We need to invalidate the canvas to redraw itself for the changes.Here we need to invalidate only when zoom is done and drag operation has happened
//or else for the Zoom which was happening in the onScale function
if((mode==DRAG_OPERATION && mScaleFactor!=1f && dragged ) || mode==ZOOM_OPERATION)
{
invalidate();
}
// we are handling the touch event
return true;
}
//Everything that is going to reflect on the screen will happen in on draw
#Override
protected void onDraw(Canvas canvas) {
//Save the canvas to set the scaling factor returned from detector
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor,gx,gy);
Log.d("Print", "mScaleFactor::" + (mScaleFactor)); //- 1) * mWidth );
Log.d("Print", " mTranslateX::" + mTranslateX + " mTranslateY::" + mTranslateY);
Log.d("Print", " ::" + mTranslateX/mScaleFactor + " mTranslateY::" + mTranslateY/mScaleFactor);
float[] matrixValues = new float[9] ;
//Check the bound that we never pan past the top of left edge of the
/* if((mTranslateX) < 0) {
mTranslateX=0;
}
////Check the right bound.
// eg : Height of display is 1280. When it is zoom by 2 it is 1280 . when it is zoom by 3 it is 2560
// Compare translateX times -1 to (scaleFactor - 1) * displayWidth.
//If translateX is greater than that value, then it has gone over the bound. So we set the value of translateX to (1 - scaleFactor) times the display width.
// Notice that the terms are interchanged... it's the same as doing -1 * (scaleFactor - 1) * displayWidth
else if((mTranslateX) > (mScaleFactor - 1) * mWidth){
mTranslateX=(mScaleFactor - 1 )* mWidth;
Log.d("Print", " InDraw mTranslateX::" + mTranslateX);
}
if((mTranslateY)< 0)
mTranslateY=0;
else if((mTranslateY) > (mScaleFactor - 1) * mHeight)
mTranslateY= (mScaleFactor-1)* mHeight;*/
if(rect.left<0 || matrixValues[Matrix.MTRANS_X]<0)
mTranslateX = mPreviousTranslateX;
else
mTranslateX = mTranslateX; // Translate value is calculated by diff from current and initial
if(rect.top<0 || matrixValues[Matrix.MTRANS_Y]<0 )
mTranslateY=mPreviousTranslateY;
else
mTranslateY = mPreviousTranslateY;
//divide by the scale factor here,
//otherwise it will end up with excessive panning based on our zoom level since the translation amount also gets scaled according to how much we've zoomed into the canvas.
canvas.translate(mTranslateX / mScaleFactor, mTranslateY / mScaleFactor);
// Draw anything more if needed here ....
// Restore the canvas to balance the save Canvas which removes all the last modification before save.
super.onDraw(canvas);
canvas.getClipBounds(rect);
Log.d("Print", " Canvas X::" + rect.left + " CanvasY::" + rect.top );
matrix = canvas.getMatrix();
Matrix m = new Matrix();
m = canvas.getMatrix();
float[] arr = new float[9] ;
m.getValues(arr );
Log.d("Print", " CanvasMatrixX::" + arr[Matrix.MTRANS_X] + " CanvasMatrixY::" + arr[Matrix.MTRANS_Y] );
canvas.restore();
}
/* #name : ZoomListener
* #description : Class which defines the listener for ScaleGestureDetector while extending abstract
*
*/
private class ZoomListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
/*
* ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
*
* #description: Method gives the scaleFactor from the detector
*
* ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
*/
#Override
public boolean onScale(ScaleGestureDetector detector) {
// getting the scaleFactor from the detector
mScaleFactor *= detector.getScaleFactor(); // gives the scaling factor from the previous scaling to the current
Log.d("Print", "detector scaling Factor" + mScaleFactor);
gx = detector.getFocusX();
gy = detector.getFocusY();
// Limit the scale factor in the MIN and MAX bound
mScaleFactor= Math.max(Math.min(mScaleFactor, MAX_ZOOM),MIN_ZOOM);
Log.d("Print", "Bounded scaling Factor" + mScaleFactor);
/*//Force canvas to redraw itself only if the one event is to happen (say Zooming only ) else do not invalidate here for multi operations
As what we de for scrolling or panning will not reflect here. So we will add this in onDraw method
invalidate();*/
// we have handle the onScale
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
super.onScaleEnd(detector);
}
}
}
I think the problem is because you are invalidating the view unnecessarily(even after limit). You should be checking the bounds in onTouch itself and make the decision of whether to redraw your view or not. You should stop redrawing once the limit is reached.
Check the value of mTranslateX in onTouch method and call invalidate.
...
if (mTranslateX >= 0 || mTranslateY >= 0) {
invalidate();
}
if (mTranslateX < 0)
mTranslateX = 0;
if (mTranslateY < 0)
mTranslateY = 0;
...
I'm trying to draw an image in a view but having problems trying to maintain the scale of the original image. Basically, I have a small view and I would like to show part of the image in the view. The intention then is to perform a translation on the image so that a different part appears in the view.
No matter what I try, either the image is down-scaled automatically to fit the view or the whole image is viewable. I've tried playing about with the settings on BitmapDrawable, ImageView and Layout to no avail.
Anyone know a good way to achieve this?
Hope This piece of code helps. I have googled it a month ago. It Scrolling performance for the Larger Images.Here whole display size is set as the Height and width of the view. You can change you know. and also can maintain the zoom controls too.
public class LargeImageScroller extends Activity {
// Physical display width and height.
private static int displayWidth = 0;
private static int displayHeight = 0;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// displayWidth and displayHeight will change depending on screen
// orientation. To get these dynamically, we should hook onSizeChanged().
// This simple example uses only landscape mode, so it's ok to get them
// once on startup and use those values throughout.
Display display = ((WindowManager)
getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
displayWidth = display.getWidth();
displayHeight = display.getHeight();
// SampleView constructor must be constructed last as it needs the
// displayWidth and displayHeight we just got.
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private static Bitmap bmLargeImage; //bitmap large enough to be scrolled
private static Rect displayRect = null; //rect we display to
private Rect scrollRect = null; //rect we scroll over our bitmap with
private int scrollRectX = 0; //current left location of scroll rect
private int scrollRectY = 0; //current top location of scroll rect
private float scrollByX = 0; //x amount to scroll by
private float scrollByY = 0; //y amount to scroll by
private float startX = 0; //track x from one ACTION_MOVE to the next
private float startY = 0; //track y from one ACTION_MOVE to the next
public SampleView(Context context) {
super(context);
// Destination rect for our main canvas draw. It never changes.
displayRect = new Rect(0, 0, displayWidth, displayHeight);
// Scroll rect: this will be used to 'scroll around' over the
// bitmap in memory. Initialize as above.
scrollRect = new Rect(0, 0, displayWidth, displayHeight);
// Load a large bitmap into an offscreen area of memory.
bmLargeImage = BitmapFactory.decodeResource(getResources(),
R.drawable.testlargeimage);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Remember our initial down event location.
startX = event.getRawX();
startY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float x = event.getRawX();
float y = event.getRawY();
// Calculate move update. This will happen many times
// during the course of a single movement gesture.
scrollByX = x - startX; //move update x increment
scrollByY = y - startY; //move update y increment
startX = x; //reset initial values to latest
startY = y;
invalidate(); //force a redraw
break;
}
return true; //done with this event so consume it
}
#Override
protected void onDraw(Canvas canvas) {
// Our move updates are calculated in ACTION_MOVE in the opposite direction
// from how we want to move the scroll rect. Think of this as dragging to
// the left being the same as sliding the scroll rect to the right.
int newScrollRectX = scrollRectX - (int)scrollByX;
int newScrollRectY = scrollRectY - (int)scrollByY;
// Don't scroll off the left or right edges of the bitmap.
if (newScrollRectX < 0)
newScrollRectX = 0;
else if (newScrollRectX > (bmLargeImage.getWidth() - displayWidth))
newScrollRectX = (bmLargeImage.getWidth() - displayWidth);
// Don't scroll off the top or bottom edges of the bitmap.
if (newScrollRectY < 0)
newScrollRectY = 0;
else if (newScrollRectY > (bmLargeImage.getHeight() - displayHeight))
newScrollRectY = (bmLargeImage.getHeight() - displayHeight);
// We have our updated scroll rect coordinates, set them and draw.
scrollRect.set(newScrollRectX, newScrollRectY,
newScrollRectX + displayWidth, newScrollRectY + displayHeight);
Paint paint = new Paint();
canvas.drawBitmap(bmLargeImage, scrollRect, displayRect, paint);
// Reset current scroll coordinates to reflect the latest updates,
// so we can repeat this update process.
scrollRectX = newScrollRectX;
scrollRectY = newScrollRectY;
}
}
}
This probably won't be the most efficient way, but if you're not going to be moving it too much, it'll do.
Treat it like a spritesheet, use Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height) to get the section you want from the original bitmap as its own bitmap, then pass that in instead. Then pass that in rather than the whole bitmap, and it'll only be working with the part you want it to show.
Sounds like you want to have a big ImageView control inside a smaller View (acting like a window), and then move the ImageView control around inside that, so only part of it is visible at a time.
Not exactly sure how you'd do that though, but pretty sure it's possible with AbsoluteLayout and a bit of tinkering.