Situtation:
I am working on a small application which should enable user to trigger something depending on which part of the screen they clicked. Imagine a picture of a teddy bear and if you click a nose it says "nose". What I have done is put in layout xml a LinearLayout. Because I have to cover different screen sizes I didn't set background in a xml layout file.
snippet form xml:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/viewMain"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="visible" >
<LinearLayout
android:id="#+id/viewThouShaltRespondToClicks"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:orientation="vertical"
android:longClickable="true"
android:visibility="visible" />
<!-- mask -->
<LinearLayout
android:id="#+id/viewInvisibleMask"
android:visibility="invisible"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
</LinearLayout>
</FrameLayout>
And then in onCreate event of activity I check the display resolution and via setBackgroundResource() set the appropriate background. I have prepared background images for each resolution.
final Point QVGA = new Point(240,320); // portrait
// Obtain the screen resolution if the device
Display defaultDisplay = ((WindowManager)this.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
Point displayResolution = new Point(defaultDisplay.getWidth(), defaultDisplay.getHeight());
if (displayResolution.equals(QVGA))
{
viewThouShaltRespondToClicks.setBackgroundResource(R.drawable.image_to_be_clicked_upon_240x320);
viewInvisibleMask.setBackgroundResource(R.drawable.mask_240x320);
}
else if ... // check for another resolution
Even if the image in the resources is not in the correct dimension it is stretched/shrinked to the screen size. This would be OK as long as the width-height ratio of background image is similar to ratio of display and the resulting image is not kewed to much. But there is a problem, explained below.
To detect which region was clicked, the region itself might be irregular shaped I have taken the following approach. I create an image – mask (bmp) with same dimensions as background image only that it has a white background and the clickable areas are in different colors. The color identifies the area. All I have to is get a coordinate of a click event (no problem here), go to the mask image and read the color of pixel on this coordinates. The problem is that the mask image is not of the correct size. On my device it is set to 1200x700, but I guess it takes on some arbitrary size on other devices.
First question: Is there a way to somehow convince the invisible layout to load background image and then stretch/shrink it to display size as it happens for visible layout by itself?
Another approach would be to load mask image (bmp, png) into some memory structure and resize it to display size.
I have tried with something like:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false; // do not scale
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
bitmapMask = BitmapFactory.decodeResource(this.getResources(), R.drawable.mask, options);
// on this place stretch shrink should follow but I have no idea how
But I don’t know how to scale Bitmap to proper size.
Any suggestions?
First question: yes, you can have a separate View in your layout containing the mask image, and set it to be invisible with android:visibility=invisible.
Second question: you can read a pixel value from the bitmap with Bitmap.getPixel(). Docs are here.
Just a simple suggestion. You could have the reference image in an imageview behind the real image. So both images are loaded in the exact same way, placed in ImageViews, and then inserted into a relativelayout. That way they should be equal size, and only one of them is visible.
This might not be a very pretty way of doing it, and i'm not sure if it will work, but you can try it out.
Related
Recently, while trying to load a image in imageView I ran into "Out of Heap Memory" error. So I looked on internet and found the way to optimise the images.
I am looking forward to finding the best way to optimise a bitmap.
Currently, I have an ImageView which has 200dp as hight and "match_parent" for width. Basically it fills the screen horizontally and takes 200dp vertically.
I am trying to achieve something like in this image.
Here is my master plan to do so.
1) Create a layout for 1 row (ImageView, TextView, Black Translucent bar behind text)
2) Use RecyclerView and inflate the data
Currently I am stuck on Step 1 (not satisfied with code).
What I did is create an XML layout for the row.
<RelativeLayout
android:id="#+id/layout_adventure_fragment"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="200dp">
<ImageView
android:id="#+id/imageView_adventure_home_screen_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:background="#drawable/textview_background_gradient"
android:layout_alignParentBottom="true"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:paddingLeft="15dp"
android:textColor="#FFFFFF"
android:textSize="25sp"
android:id="#+id/textView_adventure_name_home_screen_fragment"
android:layout_width="match_parent"
android:layout_height="55dp"
android:text="Demo Text"/>
</RelativeLayout>
I'll adjust TextView as per Typography guidelines later.
Ok, so basically ImageView is 200dp tall.
To compress my images I used the guide at Android training page which uses inSampleSize and loads image in an AsyncTask.
To calculate the required height I used something I am not sure if it is correct way or not. Since I care to compress image only for height I passed only height to calculate inSampleSize.
DisplayMetrics disp = getApplicationContext().getResources().getDisplayMetrics();
int density = disp.densityDpi;
width = disp.widthPixels;
height = (int)((float)(200.0/160.0) * (float)density);
Bitmap b =decodeSampledBitmapFromResource(getResources(),R.drawable.shake,height);
loadBitmap(R.drawable.shake,iV);
Finally I extracted thumbnail from the final bitmap for the required width and height to show user an image which fills the imageView instead of being just in the centre with margins.
imageView.setImageBitmap(ThumbnailUtils.extractThumbnail(bitmap,width,height));
Full code can be found here : http://pastebin.com/mhKi1sKd
What is the best way to optimise the imageView so that in case multiple images are shown in a RecyclerView, Heap memory does not get full and also if there are unnecessary codes in my program that simply take up processing and valuable time?
This is what I have achieved till now.
Have a look at Picasso library.
Many common pitfalls of image loading on Android are handled
automatically by Picasso:
Handling ImageView recycling and download cancelation in an adapter.
Complex image transformations with minimal memory use.
Automatic memory and disk caching.
I want to add an ImageButton to my layout that has the effect of scaling down in order to look like it's being pressed. So I basically have the original image #drawable/scan and the pressed image which is #drawable/scanhot. Scanhot is basically the same image scaled down.
My code is the following but it doesnt seem to work.
As you can see I am using a Button as opposed to an ImageButton because a Button could at least "switch" different images. However it does not do anything with similar images like scan/scanhot
button_rescan.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="#drawable/scanhot"/>
<item
android:drawable="#drawable/scan"/>
</selector>
layout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
android:background="#android:color/transparent">
<Button
android:id="#+id/fragment_radar_rescan_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/button_rescan"
android:layout_gravity="center_horizontal"
android:layout_centerHorizontal="true"/>
</RelativeLayout
It looks that drawable/scanhot are not the same size drawable/scan (in pixels). So if you want effect of "zoom" (or whatever) you should still keep image size the same for both images (i.e. 100x100) and with the same size make scanhot look smaller. If you already got images scalled as you want, just load the smaller one to Gimp or Photoshop and scale its canvas to the size of the other image (which is bigger I assume) and then save. In that case canvas for both will be the same and you avoid additional scalling from ImageView
The problem with what you are doing is that when you set a picture to a background, it automatically is blown up to fit the View. Instead, both pictures should have the same resolution, but the picture in scanhot should just look smaller.
In otherwords, both pictures should have a 132x29 resolution (or rather, they should both be the same size)
I have an image like this used as background in a RelativeLayout:
This image is used as background for all the levels of my game. Every level is drawn onto the blue area.
I want to keep fixed the aspect-ratio of the blue area, changing the size of the red edges to avoid to show to the user unused pixels of their screen. The green area must be fixed to 80dp for all phones. Then I must add a View (a GLSurfaceView) in my layout in such a way that it fit perfectly the blue area. Thus all levels of my Android game will be perfectly the same in all Android device.
How can I solve this problem?
The real image that I use is a little more complex. You can look it here:
Real image
I would use a FrameLayout for the middle part of the screen(blue), add an ImageView, containing the BackgroundImage you want to display, and put the GLSurfaceView on top of it.
Since the aspect ratio is always the same, you could set the ImageViews sclaing to fit xy and the image should always look the same.
Lets assume you are using a simple SurfaceView, the xml code id use to put a ImageView begind it would look like this
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_height="match_parent"
android:layout_width="match_parent"/>
<SurfaceView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
As i dont know how you build your View i cant post the code that does the job, but just add a FrameLayout instead of your GLSurfaceView to your View, with the Same Dimensions, the GLSurfaceView would have.
To that FrameLayout first add the ImageView, then the GLSurfaceView. Both with height and width set to match_parent.
To Figure out the size of your SurfaceView...
Retrieve Display Dimensions
Substract Green Bar Dimensions
Calculate the size of the Blue View, get the Height/Width (whatever is bigger) calculate the missing Dimension
Set the Red Views to Occupie the empty space.
So you would have to do this programmatically :)
My app has an Activity that displays a vertically scrolling list of ImageButtons. I want each buttons image to (A) come from the assets folder and (B) retain it's aspect ratio as it scales. Unfortunately, my ImageButton doesn't size correctly when it's image comes from the assets folder.
ImageButton src set from drawable
The first screenshot is my test app where the images all come from my apps drawables. That's the "correct" aspect ratio for that image, which is what I want to keep (all of the image buttons have been given scaleType "fitXY" "centerCrop").
ImageButton src set from assets
The second screenshot is my test app where the images all come from my apps "assets" folder — as you can see, the images are stretched out to the full width of the screen as desired, but the original aspect ratio has been lost:
Activity code (MainActivity.java)
LinearLayout buttons = (LinearLayout) findViewById(R.id.buttons);
View button = layoutInflater.inflate(R.layout.snippet_imagebutton, null);
ImageButton imageButton = (ImageButton) button.findViewById(R.id.imageButton);
if (GET_IMAGE_FROM_ASSETS) {
InputStream s = assetManager.open("image.png");
Bitmap bmp = BitmapFactory.decodeStream(s);
imageButton.setImageBitmap(bmp);
} else {
imageButton.setImageResource(R.drawable.image);
}
TextView buttonText = (TextView) button.findViewById(R.id.textView);
buttonText.setText("Button text!");
buttons.addView(button,
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
Button layout (snippet_imagebutton.xml)
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<ImageButton
android:id="#+id/imageButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:gravity="center"
android:textColor="#FFF"
android:background="#88000000"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
Dodgy hack
I've found a dodgy hack which achieves what I want and illustrates the problem — the ImageButton will size correctly to match a scaled up image from the resources, but it won't size correctly to match a scaled up image from the assets. I'd prefer a "real" solution over the dodgy hack if there is one. :)
// to start with, scale the ImageButton based on an image in our
// resources that has the same dimensions as the image in our assets
imageButton.setImageResource(R.drawable.image);
// when we eventually render (we don't have width / height yet)...
imageButton.post(new Runnable() {
public void run() {
int height = imageButton.getHeight();
int width = imageButton.getWidth();
// replace our "resource" image with our "asset" image
imageButton.setImageBitmap(bmp);
// and force the ImageButton to keep the same scale as previously
imageButton.setLayoutParams(
new RelativeLayout.LayoutParams(width, height));
}
});
Summary
I want to get the images for these buttons from my apps "assets" folder. How do I fix my app so that the button images are all scaled properly (i.e. retain their original aspect ratio)?
I'm assuming this has something to do with the framework not actually knowing the width / height of the image before it renders the ImageButton to screen — so how do I fix that? I tried setting adjustViewBounds to "true" on both the RelativeLayout and the ImageButton itself, but that didn't seem to have any effect.
The aspect ratio is not kept when you give the scale type attribute as "fitXY". Try "center", "center_crop" or "center_inside" according to your needs. Check ImageViewScaleType as well.
From what I gather, AdjustViewBounds is actually the method that provides the exact functionality you need. However, as Roman Nurik explains here, AdjustViewBounds does NOT increase the ViewBounds beyond the natural dimensions of the drawable.
Without seeing how you allocated resources/assets, my suspicion would be that this is why you see a difference when loading from Resources and when loading from Assets. Your resource file is - presumably - being scaled up when you load it (since that is the default behavior for resources), so that the actual size of the drawable returned from /res is significantly larger than the bitmap you decode from /assets.
Overriding ImageButton (as Nurik suggests), is of course one option, but probably overkill.
Assuming I am correct about the problem, you should be able to fix it simply by setting your BitmapFactory.options correctly when you load the Assets file. You need to set inDensity and inTargetDensity correctly according to your device (get DisplayMetrics), and set inScaled to true.
Alternatively, you can always rescale the bitmap manually before loading it on the ImageButton. Just grab the screen width to determine how large you need to make it.
P.S.
Auto-scaling is obviously the elegant solution, but it does have a weakness (which applies equally to both resources and assets) - if the width of your display > bitmap witdh when scaled up, you'll likely still have the aspect ratio messed up. You can check this pretty easily by going to landscape mode and seeing whether the resource file still result in the right aspect ratio. If you have a large enough base image (or use different layouts according to the screen size/orientation), then this should not be a problem though. Just something to keep in mind.
My issue is this:
I use the android emulator, targeting android 2.2.
I created a custom view which I want to handle the background of my application, in this view I only want to display an image but I want it to fill the entire screen.
I initialize the view from my "Activity" like this:
BackgroundView bw = new BackgroundView(this);
The problem:
On my HDD, the resource image I'm using is 500x375 pixels. After calling:
Bitmap m_resourceBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.landscape_1);
int nWidth = m_resourceBitmap.getWidth();
int nHeight = m_resourceBitmap.getHeight();
the returned nWidth and nHeight depend on where I placed the resource, if i place it in the "drawable-mdpi" I get the original size, otherwise I get other sizes.
On the other hand if I ask the view for it's width and height via
this.getWidth();
this.getHeight();
I get fixed values, but different from the resolution I expected from the device (I set a 480x800 device and I get 320x533)
and finally if I try to create a scale matrix using view dimensions divided by the image dimensions, the result, on screen, is an image much smaller than the screen...
My question is:
What questions should I ask and from which objects so that inside a View I can draw a resource Bitmap so that it occupies the entire screen?
Cant you do it all in the layout XML file? (Here's an example that I use for my menu background image)
<ImageView android:id="#+id/menuBackground"
android:src="#drawable/imageResource"
android:adjustViewBounds="true"
android:gravity="center_vertical"
android:scaleType="fitXY"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_x="0dp"
android:layout_y="0dp" />
Then if you need to change it in runtime all you have to do is change the drawable.