So Im making a start screen for a game. At the moment all it has is the background image and the button image. Ill post my code below and then explain the issues...
public class TitleView extends View {
private Bitmap titleGraphic;
private Bitmap scaledTitleGraphic;
private Bitmap playButtonUp;
private Bitmap scaledPlayButton;
private Bitmap playButtonDown;
private Bitmap scaledPlayButtonDown;
private Boolean playButtonPressed = false;
private int screenW;//storing width...
private int screenH;//storing height...
public TitleView(Context context) {
super(context);
titleGraphic = BitmapFactory.decodeResource(getResources(), R.drawable.title_graphic);
//scaledTitleGraphic = Bitmap.createScaledBitmap(titleGraphic,screenH,screenH,true);
playButtonUp = BitmapFactory.decodeResource(getResources(), R.drawable.play_button_up);
scaledPlayButton = Bitmap.createScaledBitmap(playButtonUp, 400, 125, true);
playButtonDown = BitmapFactory.decodeResource(getResources(),R.drawable.play_button_down);
scaledPlayButtonDown = Bitmap.createScaledBitmap(playButtonDown, 400, 125,true);
}
#Override //gets the width and height...
public void onSizeChanged (int w, int h, int oldw, int oldh){
super.onSizeChanged(w, h, oldw, oldh);
screenW = w;
screenH = h;
}
#Override
protected void onDraw(Canvas canvas) {
//canvas.drawBitmap(titleGraphic, (screenW - titleGraphic.getWidth())/2, 0, null);
//canvas.drawBitmap(scaledPlayButton, (screenW - playButtonUp.getWidth())/2, buttonPosition , null);
scaledTitleGraphic = Bitmap.createScaledBitmap(titleGraphic,screenW,screenH,true);
canvas.drawBitmap(scaledTitleGraphic, 0, 0, null);
if(playButtonPressed){
canvas.drawBitmap(scaledPlayButtonDown, (screenW - scaledPlayButtonDown.getWidth())/2, (int) (screenH*0.7) , null);
}else{
canvas.drawBitmap(scaledPlayButton, (screenW - scaledPlayButton.getWidth())/2, (int) (screenH*0.7) , null);
}
}
public boolean onTouchEvent(MotionEvent event) {
int eventaction = event.getAction();
int X = (int)event.getX();
int Y = (int)event.getY();
switch (eventaction ) {
case MotionEvent.ACTION_DOWN:
if(((X > ((screenW-playButtonUp.getWidth())/2)) && (X < (((screenW-playButtonUp.getWidth())/2) +
playButtonUp.getWidth()))) && ((Y > (int)(screenH*0.7)) && (Y < (int)(screenH*0.7) +
playButtonUp.getHeight()))){
playButtonPressed = true;
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
playButtonPressed = false;
break;
}
invalidate();
return true;
}
}
Is this the best way to do this? First of all when I put the background it didnt fit so i used the createscaledbitmap method. Is this popularly used or should i make perfect resolution images some how?? I feel a little weird defining two bitmaps one for the original image and then the other for the scaled image.
Secondly I faced a similar problem with the button. Initially when I put my button on it was huge covered most of my screen so i thought i should make it smaller and i went in photo shop and made it 100x40 but that was still too huge? So I also had to scale the bitmap. Im just confused I mean... I know that its good practice to put images of all resolutions to target devices with different resolutions but i feel like my approach isnt correct because in this case i should just put one HUGE image and just scale all the time?
What Im trying to say is...Im following this from a book but its a little old the book a few years old is there a better approach I should know about? I mean for example I want to use an on click listener etc but instead in this approach im using onTouchEvent?
Related
What I want to do is to click a space on the screen, get the coordinates and paint the image here. I did this using paintIcon and mouseListener in eclipse but how to do the same thing in android studio? Thanks!
public static Game game = new Game();
public Control(){
this.setLayout(null);
this.setBounds(0, 0, 780, 780);
addMouseListener(this);
}
public void paint(Graphics g){
setBackground(Color.GREEN);
board.paintIcon(this, g, 0, 0);
for(int i = 0; i < 19; i++){
for(int j = 0; j < 19; j++){
if(game.gameBoard.board[i][j] != 'E'){
if(game.gameBoard.board[i][j] == 'B'){
black.paintIcon(this, g, j * 40, i * 40);
}
if(game.gameBoard.board[i][j] == 'W'){
white.paintIcon(this, g, j * 40, i * 40);
}
}
}
}
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
mouseX = e.getX();
mouseY = e.getY();
int targetX = mouseX / 40;
int targetY = mouseY / 40;
game.move(targetX, targetY);
repaint();
}
In Android you use a different system than in "Java"(you know Android is Java), for instance as is clear from your code you do not have a Mouse but a Touch event.
The first thing you should do is to study this official link that explains the concept of Canvas. Then you will learn how to integrate the "tap" into your app that is instead this official link
This is an excellent post to learn ho to do it.
You begin extending the View and overriding the method onDraw(Canvas canvas) then you need to override a listener that say you when the user clicked the screen onTouchEvent(MotionEvent event) usually there are three motion events you want to cover MotionEvent ACTION_DOWN(you tap and the screen recognizes the X,Y coordinates, that is the one you need),ACTION_MOVE(if you drag your finger maintaining pressure on the screen) and ACTION_UP(when you release your finger)
Just remember you need to call invalidate() if you have an animation or something changes on the screen. Basically is a "forced" call to the "onDraw" method. On the basis of the three links I sent you can cover all thebasis of the 2d graphics in Android that is a bit different by Java because of the different features but also the Android dependency on the specific SDK classes
what I want to do is to click a space on the screen, get the coordinates and paint the image here
Here is an example of how you can obtain that in Android instead of "just" Java, please see the notes comments I did below with the double slashes //
public class YourBoard extends View {//EXTENDS VIEW this is important because you override methods
Drawable editIcon = getResources().getDrawable(R.drawable.icon);
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.background);
float xPos = 0;
float yPos = 0;
public YourBoard (Context context) {//HERE THE CONSTRUCTOR YOU CAN INITIALIZE THINGS HERE
super (context);
}
#Override
protected void onDraw (Canvas canvas) {//This was your paint(Graphics g)
super.onDraw(canvas);
canvas.save();
canvas.drawBitmap(mBitmap, 0, 0, null);
canvas.translate(xPos, yPos);
editIcon.draw(canvas);
canvas.restore();
}
#Override
public boolean onTouchEvent (MotionEvent event) {//THIS WAS YOUR MOUSE LISTENER
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN :
xPos = event.getX();
yPos = event.getY();
invalidate();//NOTICE THE INVALIDATE I MENTIONED
break;
}
return true;
}
}
}
I have created an app that actually uses flood fill algorithm to fill colors in bitmaps. I have created some bitmaps (200x200) but I don't know the exact size of bitmap that I should create, I want bitmaps to cover full screen and when I scale bitmaps, they become blur and flood fill doesn't work on them. I saw an app that used GridView to show images and click on image started new activity with image covering full screen. How can I achieve this. Attached is CustomImage that I've use to show bitmap. Any help will be appreciated.
EDITED: I know that GridView doesn't scale up image to full screen, it use another image. That app behaves same for different screen size, those image fill screen of any size without effecting the quality.
public class CustomImage extends View {
public CustomImage(Context context) {
super(context);
} // end constructor
public CustomImage(Context context, int resource_id) {
super(context);
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
mPoint = new Point();
mProgressIndicator = (ProgressIndicator) context;
mBitmap = BitmapFactory.decodeResource(getResources(), resource_id)
.copy(Bitmap.Config.ARGB_8888, true);
} // end constructor
#Override
protected void onDraw(Canvas canvas) {
// draw image
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
} // end onDraw
#Override
public boolean onTouchEvent(MotionEvent event) {
int bWidth = mBitmap.getWidth();
int bHeight = mBitmap.getHeight();
mPoint.x = (int) event.getX();
mPoint.y = (int) event.getY();
mPoint.x = (mPoint.x > bWidth) ? (bWidth - 5) : mPoint.x;
mPoint.y = (mPoint.y > bHeight) ? (bHeight - 5) : mPoint.y;
switch (event.getAction()) {
// called when screen clicked i.e New touch started
case MotionEvent.ACTION_DOWN:
mProgressIndicator.updateProgress(0);
new Filler(mBitmap, mPoint, mPaint.getColor(), mBitmap.getPixel(
mPoint.x, mPoint.y), this).execute();
invalidate();
} // end Case
return true;
} // end onTouchEvent
public void setColor(int color) {
mPaint.setColor(color);
} // end setColor
} // end Class
The GridView you have seen might be using two versions of the same image. One as a big image and another as a scaled down thumbnail image for the grid.
I was a frequent guest at stackoverflow until I ran into a problem that I really couldn't find anything existing about. So here is my first question:
I am building a camera app in which the user can take several pictures before proceeding to the next step. I want to give the user the possibility to review and delete pictures while stying in the camera stage, so I have written a custom View to show Thumbnails of the already captured images with a delete button. These "Thumbviews" are contained in a LinearLayout that is located on top of the camerapreview-SurfaceView and has a default visibility of "GONE". The user can toggle the visibility with a button.
It all works fine, but I have one problem:
When I take more than about 10 pictures, I get an OutOfMemoryError. The thumbnails are really small and don't take a lot of memory and also I recycle the original Bitmaps and perform a System.gc() after creating the thumbs.
The weird thing is, when I press the button that sets the visibility of the containing LinearLayout to "VISIBLE" and again to "GONE", apparently all the memory gets freed and I can take many more pictures than 10.
I've tried switching the visibility in code but that doesn't work, and also destroying the drawing cache.
There has to be another way to free that memory besides pushing my visibility button 2 times ;-)
Here's the code for the ThumbView:
public class ThumbView extends View {
private Bitmap mBitmap;
private Bitmap mScaledBitmap;
private int mWidth, mHeight, mPosX, mPosY;
static private Bitmap mDeleteBitmap;
private File mPreviewFile;
private File mFinalFile;
private Orientation mOrientation;
private boolean mRed;
public ThumbView(Context context, Bitmap bitmap, File previewFile, File finalFile, Orientation orientation) {
super(context);
mBitmap = bitmap;
mPreviewFile = previewFile;
mFinalFile = finalFile;
mOrientation = orientation;
if(mDeleteBitmap != null)
return;
mDeleteBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.deletebutton);
}
public void deleteFile()
{
if(mPreviewFile != null && mPreviewFile.exists())
{
mPreviewFile.delete();
}
if(mFinalFile != null && mFinalFile.exists())
{
mFinalFile.delete();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(mWidth, mWidth);
if(mBitmap == null)
return;
mHeight = mWidth;
float bitmapRatio = mBitmap.getWidth() / (float) mBitmap.getHeight();
if(bitmapRatio > 1)
{
mScaledBitmap = Bitmap.createScaledBitmap(mBitmap, mWidth,
(int)(mWidth/bitmapRatio), true);
mPosY = (mWidth-mScaledBitmap.getHeight())/2;
}
else
{
mScaledBitmap = Bitmap.createScaledBitmap(mBitmap, (int)(mHeight*bitmapRatio),
mHeight, true);
mPosX = (mHeight-mScaledBitmap.getWidth())/2;
}
Matrix mtx = new Matrix();
mtx.postRotate(-90);
Bitmap b = Bitmap.createBitmap(mScaledBitmap, 0, 0, mScaledBitmap.getWidth(), mScaledBitmap.getHeight(), mtx, true);
mScaledBitmap = b;
b = null;
mBitmap.recycle();
mBitmap = null;
System.gc();
}
public boolean deleteButtonPressed(float x, float y)
{
Rect r = new Rect(mPosY, mPosX, mPosY+mDeleteBitmap.getWidth(),
mPosX+mDeleteBitmap.getHeight());
if(r.contains((int)x, (int)y))
{
return true;
}
return false;
}
public void setRed(boolean red)
{
mRed = red;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mScaledBitmap, mPosY, mPosX, new Paint());
canvas.drawBitmap(mDeleteBitmap, mPosY, mPosX, new Paint());
if(mRed)
canvas.drawColor(0x55FF0000);
}
}
The "why does it not break" answer's easy. When the visibility of a child view (or container) is set to GONE, the parent layout will (generally) skip it and not even bother rendering it. It's not "hidden", it's not there at all.
If your thumbnails are really thumbnails you shouldn't be running out of memory, however, I think you're not downsampling them (I could be wrong). How are you showing them? You should share that piece of code. (New Photo -> Thumbnail Image -> Image View)
I am so stupid. Obviously my onMeasure() won't be called while the View stays GONE and therefore the original bitmap stays in memory. I changed visibility to INVISIBLE and everything works fine now.
My home automation app has a feature where people can upload images to their phone with floorplans and dashboards that they can use to control their home automation software. I have them upload two images: one visible image with the graphics that they want displayed, and a second color map with solid colors corresponding to the objects they want to target areas from the visible image. Both images have to be the same size, pixel-wise. When they tap the screen, I want to be able to get the color from the colormap overlay, and then I proceed to do what ever action has been associated with that color. The problem is, the scaling of the image is screwing me up. The images that they use can be larger than the device screen, so I scale them so they will fit within the display. I don't really need pinch to zoom capability right now, but I might implement it later. For now, I just want the image to be displayed at the largest size so it fits on the screen. So, my question is, how could I modify this code so that I can get the correct touchpoint color from the scaled image. The image scaling itself seems to be working fine. It is scaled and displays correctly. I just can't get the right touchpoint.
final Bitmap bm = decodeSampledBitmapFromFile(visible_image, size, size);
if (bm!=null) {
imageview.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
imageview.setImageBitmap(bm);
}
final Bitmap bm2 = decodeSampledBitmapFromFile(image_overlay, size, size);
if (bm2!=null) {
overlayimageview.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
overlayimageview.setImageBitmap(bm2);
imageview.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent mev) {
DecodeActionDownEvent(v, mev, bm2);
return false;
}
});
}
private void DecodeActionDownEvent(View v, MotionEvent ev, Bitmap bm2)
{
xCoord = Integer.valueOf((int)ev.getRawX());
yCoord = Integer.valueOf((int)ev.getRawY());
try {
// Here's where the trouble is.
// returns the value of the unscaled pixel at the scaled touch point?
colorTouched = bm2.getPixel(xCoord, yCoord);
} catch (IllegalArgumentException e) {
colorTouched = Color.WHITE; // nothing happens when touching white
}
}
private static Bitmap decodeSampledBitmapFromFile(String fileName,
int reqWidth, int reqHeight) {
// code from
// http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(fileName, options);
}
private static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// code from
// http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
I figured it out. I replaced
xCoord = Integer.valueOf((int)ev.getRawX());
yCoord = Integer.valueOf((int)ev.getRawY());
with
Matrix inverse = new Matrix();
v.getImageMatrix().invert(inverse);
float[] touchPoint = new float[] {ev.getX(), ev.getY()};
inverse.mapPoints(touchPoint);
xCoord = Integer.valueOf((int)touchPoint[0]);
yCoord = Integer.valueOf((int)touchPoint[1]);
After 7 years I have to provide another answer, because after upgrading to LG G8s from LG G6 the code from accepted answer stopped returning the correct color.
This solution works on all the phones I found at home, but who knows... maybe even this one is not universal.
(Using Xamarin C# but the principle is easy to understand)
First we have to get the actual ImageView size as float to get floating point division
private float imageSizeX;
private float imageSizeY;
private void OnCreate(...) {
...
FindViewById<ImageView>(Resource.Id.colpick_Image).LayoutChange += OnImageLayout;
FindViewById<ImageView>(Resource.Id.colpick_Image).Touch += Image_Touch;
...
}
//Event called every time the layout of our ImageView is updated
private void OnImageLayout(object sender, View.LayoutChangeEventArgs e) {
imageSizeX = Image.Width;
imageSizeY = Image.Height;
Image.LayoutChange -= OnImageLayout; //Just need it once then unsubscribe
}
//Called every time user touches/is touching the ImageView
private void Image_Touch(object sender, View.TouchEventArgs e) {
MotionEvent m = e.Event;
ImageView img = sender as ImageView;
int x = Convert.ToInt32(m.GetX(0));
int y = Convert.ToInt32(m.GetY(0));
Bitmap bmp = (img.Drawable as BitmapDrawable).Bitmap;
// The scale value (how many times is the image bigger)
// I only use it with images where Width == Height, so I get
// scaleX == scaleY
float scaleX = bmp.Width / imageSizeX;
float scaleY = bmp.Height / imageSizeY;
x = (int)(x * scaleX);
y = (int)(y * scaleY);
// Check for invalid values/outside of image bounds etc...
// Get the correct Color ;)
int color = bmp.GetPixel(x, y);
}
Hopefully this is the last step in completing my app. I need to make a bitmap within a canvas clickable that will either call a new activity that will play a video (mp4) or within the current activity play the video.
The class that displays the canvas and bitmaps is a class I use over and over to display a full image of a thumbnail. The image id is passed through an Intent. Here is the code for the full image activity (I'm very much a noob and pieced my code together one step at a time using this site and others so I apologize if it's not clean):
public class full_image extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(new BitmapView(this));
getWindow().setBackgroundDrawableResource(R.drawable.bground);
getWindow().setWindowAnimations(0);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
class BitmapView extends View {
public BitmapView(Context context) {
super(context);
}
#Override
public void onDraw(Canvas canvas) {
int imgid = getIntent().getIntExtra("Full",0);
Bitmap fullimage = BitmapFactory.decodeResource(getResources(),imgid);
Bitmap playButton = BitmapFactory.decodeResource(getResources(), R.drawable.bt_play); //Added for Video
Display display = getWindowManager().getDefaultDisplay();
int fpWidth = fullimage.getWidth();
int fpHeight = fullimage.getHeight();
int playWidth = playButton.getWidth();
int playHeight = playButton.getHeight();
int screenWidth = display.getWidth();
int screenHeight = display.getHeight();
int leftPoint = screenWidth/2 - fpWidth/2;
int topPoint = screenHeight/2 - fpHeight/2;
int leftPlayPoint = screenWidth/2 - playWidth/2;
int topPlayPoint = screenHeight/2 - playHeight/2;
canvas.drawBitmap(fullimage,leftPoint,topPoint,null);
canvas.drawBitmap(playButton,leftPlayPoint,topPlayPoint,null);
}
}
}
If possible, I would like just the playButton to house the onClickListener but if it's easier, I'm Ok with making the whole canvas clickable (if that's even possible).
I read on another question where there was a suggestion to use TouchEvent. I tried going that route but could not get it to work. Is this the right path and I just need to play around with it more to get it to work?
Thanks, J
Additional Stuff:
here's a snippet of code I found in another question. Where would I put this code into the code provided above.
public boolean onTouchEvent(MotionEvent event){
int action = event.getAction();
int x = event.getX() // or getRawX();
int y = event.getY();
switch(action){
case MotionEvent.ACTION_DOWN:
if (x >= xOfYourBitmap && x < (xOfYourBitmap + yourBitmap.getWidth())
&& y >= yOfYourBitmap && y < (yOfYourBitmap + yourBitmap.getHeight())) {
//tada, if this is true, you've started your click inside your bitmap
}
break;
}
}
I think we can't setOnClick on bitmap in canvas.
But we can use onTouch method for it. and check touch on bitmap or not using x-y position of touch.
try this code in onTouch method for get touch on bitmap...
if (x >= xOfYourBitmap && x < (xOfYourBitmap + yourBitmap.getWidth())
&& y >= yOfYourBitmap && y < (yOfYourBitmap + yourBitmap.getHeight())) {
//tada, if this is true, you've started your click inside your bitmap
}