THE PROBLEM
I'm try to save a view group (which has a CardView as one of its childern) as a PNG file. To achieve this,
I inflate the view group and populate the views with required information
Load an image to an image view via Glide
Add a ViewTreeObserver.OnGlobalLayoutListener to the ViewTreeObserver of the image view and pass the entire (parent) view that is going to be shared to a method that converts the view to a bitmap when image view's bottom is greater than zero (image view's height attribute is set to wrap_content, thus its bottom will be zero until image is loaded).
By doing this, I'm able to convert the view to a bitmap, however, with one caveat: the CardView's show is not rendered on the bitmap.
FAILED ATTEMPTS
So far I've tried:
Switching between layerType attribute from "software" to "hardware".
Setting on and off cardUseCompatPadding attribute of the CardView.
Tried setting the image drawable without using Glide.
Tried without loading an image drawable at all.
THE CODE
Here are code snippets that might help you guys identify the problem:
The method used to convert a view to a bitmap
public static Bitmap getBitmapFromView(View view) {
//Define a bitmap with the same size as the view
Bitmap b = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
//Bind a canvas to it
Canvas canvas = new Canvas(b);
//Get the view's background
Drawable bgDrawable = view.getBackground();
if (bgDrawable != null)
//has background drawable, then draw it on the canvas
bgDrawable.draw(canvas);
else
//does not have background drawable, then draw white background on the canvas
canvas.drawColor(Color.WHITE);
// draw the view on the canvas
view.draw(canvas);
//return the bitmap
return b;
}
XML layout file of the view that's being inflated and passed to the getBitmapFromView() above.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="16dp">
<com.devspark.robototextview.widget.RobotoTextView
android:id="#+id/title"
style="#style/text_subhead"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginBottom="#dimen/lessons_horizontal_margin_narrow"
android:layout_marginLeft="#dimen/lessons_horizontal_margin_narrow"
android:layout_marginRight="#dimen/lessons_horizontal_margin_narrow"
android:layout_marginTop="#dimen/lessons_horizontal_margin_narrow"
android:gravity="left"
app:typeface="roboto_medium" />
<com.devspark.robototextview.widget.RobotoTextView
android:id="#+id/text"
style="#style/text_subhead"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/lessons_horizontal_margin_narrow"
android:layout_marginRight="#dimen/lessons_horizontal_margin_narrow"
android:gravity="left"
android:textColor="#color/text"
app:typeface="roboto_regular" />
<android.support.v7.widget.CardView
android:id="#+id/image_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="#dimen/lessons_horizontal_margin_narrow"
app:cardCornerRadius="#dimen/lessons_image_card_corner_radius"
app:cardElevation="3dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.makeramen.roundedimageview.RoundedImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="#null"
app:riv_corner_radius_top_left="#dimen/lessons_image_card_corner_radius"
app:riv_corner_radius_top_right="#dimen/lessons_image_card_corner_radius" />
<com.devspark.robototextview.widget.RobotoTextView
android:id="#+id/caption"
style="#style/text_caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="#dimen/lessons_image_card_caption_margin"
android:gravity="left"
app:typeface="roboto_condensed_regular" />
</LinearLayout>
</android.support.v7.widget.CardView>
<!-- Some other views that aren't particularly interesting -->
</LinearLayout>
just change cardview to view, and set
android:background="#android:drawable/dialog_holo_light_frame"
ofcause you need to deal the padding yourself
As #galex said - shadows are not drawn on views only by calling measure and layout.
So we can't use the elevation. Also we can't use the drawable shadow, because then we get sharp angles and straight sides.
Solution: use the png 9-path resizable drawable. For this we can use this beautiful tool: Android shadow generator
Create 9-path files for all of mdpi, hdpi, xhdpi, xxhdpi and xxxhdpi.
Put all of your png`s to res/drawables folder.
Now we can use this drawable like background for view where we want to see shadow.
Note that for different densities, you must change following parameter: height and width of the view, shadow offsets (x and y), blur, round corners radius and paddings.
Multipliers for diffrent densities:
LDPI - x0.75//practically not used, so you can do without it
MDPI - x1.0// means original size
HDPI - x1.5
XHDPI - x2.0
XXHDPI - x3.0
XXXHDPI - x4.0
For example if you need to create rectangle with 30dp x 100dp and radius = 8dp
list of 9.path images you should generate:
30х100px, rad = 8
45х150px, rad = 12
60х200px, rad = 16
90х300px, rad = 24
120х400px, rad = 32
I have a same problem. So after searching, I founded this. Hope this help
https://medium.com/#ArmanSo/take-control-of-views-shadow-android-c6b35ba573e9
Related
For a layout in a prototype I need a button that overlays a section (an inner rectangle) of an ImageView with an image that is scaled-up preserving aspect ratio.
Is there some way to overlay the button over the imageview with defined margins, then scale up this combination preserving the margins? I tried putting both in an additional relativelayout, but the child elements did not scale within the relativelayout (like 2 UIViews might scale when scaling the parent UIView). I also tried hooking into the imageview using adjustViewBounds to shrink the imageview to the scaled-up image, but this didn't work either.
Here is the current layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp"
tools:context=".MainActivity" >
<ImageView
android:id="#+id/placeholderImage"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="#drawable/placeholder"
android:padding="0dp"
android:adjustViewBounds="true"/>
<Button
android:id="#+id/btn"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#android:color/darker_gray"
android:onClick="show"/>
</RelativeLayout>
I ended up doing this programmatically using some of the answers here:
Find the position of a bitmap...
To summarize the approach:
in onCreate use a globalLayoutListener
in the onGlobalLayout callback, get the scale of the drawable using getImageMatrix, then getValues. Since I'm maintaining aspect ratio either MSCALE_X or MSCALE_Y will work.
get the bounds of the drawable, which will vary from the resource png's dimensions due to pre-scaling for target density. Since the resource png's size is known, compute this 'prescale' and multiply by the Matrix scale to get totalScale
apply totalScale to the button size and offsets using LayoutParams.
I am currently using a custom ArrayAdapter for a list. List items span the whole width of the screen so on some devices are very wide.
I overlay an ImageView on some of the list items which you can swipe to dismiss.
The xml for the ImageView is below:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:scaleType="matrix"
android:src="#drawable/myImage" />
The view is placed correctly, but in order for the image to fit devices with really wide screens, or in landscape mode, the image has to be very wide.
The problem is, I want to scale the image to fit vertically (i.e. fitY which doesn't exist as a scaleType, then to anchor the image to the left of the view, and for everything that doesn't fit to be cropped.
I have tried all of the scaleType values, and none of them are quite right. As such, I felt I should probably use matrix and define my own translation for the image, so I tried this in getView:
//snipped out code before to initiate variables etc.
Drawable src = imageView.getDrawable();
//scale by the ratio of the heights.
int scaleFactor = imageView.getHeight() / src.getIntrinsicHeight();
Matrix imageMatrix = new Matrix();
//set the scale factor for the imageMatrix
imageMatrix.setScale(scaleFactor, scaleFactor);
//set the translation to be 0,0 (top left);
imageMatrix.setTranslate(0,0);
//assign the image matrix to the imageview.
imageView.setImageMatrix(imageMatrix);
But this is not having any effect at all (even if I make the values really crazy). I presume I am setting the imageMatrix in the wrong place - is there an onDraw() event I can hook into for a custom ArrayAdapter subclass?
Or am I going about solving the problem in completely the wrong way?
In the end I had to explicitly set imageView.setScaleType(ScaleType.MATRIX); in code, then apply my imageMatrix to it. Using the xml attribute scaleType="matrix" didn't seem to work.
The fitXY scaleType only work when you set the layout_width and layout_height to match_parent or fill_parent according to the API level that your are using for.
so you must use this code in your xml only:
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:scaleType="fitXY"
android:src="#drawable/myImage" />
How do I make a background image fit the view but keep its aspect ratio when using <bitmap /> as a background drawable XML?
None of <bitmap>'s android:gravity values gives the desired effect.
It is impossible to achieve manipulating background attribute within xml-files only. There are two options:
You cut/scale the bitmap programmatically with
Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight,
boolean filter) and set it as some View's background.
You use ImageView instead of background placing it as the first layout's element and specify android:scaleType attribute for it:
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/backgrnd"
android:scaleType="centerCrop" />
...
rest layout components here
...
</RelativeLayout>
There is an easy way to do this from the drawable:
your_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="#color/bg_color"/>
<item>
<bitmap
android:gravity="center|bottom|clip_vertical"
android:src="#drawable/your_image" />
</item>
</layer-list>
The only downside is that if there is not enough space, your image won't be fully shown, but it will be clipped, I couldn't find an way to do this directly from a drawable. But from the tests I did it works pretty well, and it doesn't clip that much of the image. You could play more with the gravity options.
Another way will be to just create an layout, where you will use an ImageView and set the scaleType to fitCenter.
Hope this information helps you achieve what you want.
I wanted to do something similar in my custom Drawable class.
Here are the important pieces:
public class CustomBackgroundDrawable extends Drawable
{
private Rect mTempRect = new Rect();
private Paint mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
...
public void draw(#NonNull Canvas canvas)
{
Rect bounds = getBounds();
if (mBitmap != null ) {
if (mScaleType == ScaleType.SCALE_FILL) {
//bitmap scales to fill the whole bounds area (bitmap can be cropped)
if (bounds.height() > 0 && bounds.height() > 0) {
float scale = Math.min(mBitmap.getWidth()/(float)bounds.width(), mBitmap.getHeight()/(float)bounds.height());
float bitmapVisibleWidth = scale * bounds.width();
float bitmapVisibleHeight = scale * bounds.height();
mTempRect.set((int)(mBitmap.getWidth()-bitmapVisibleWidth)/2, 0, (int)(bitmapVisibleWidth+mBitmap.getWidth())/2, (int)bitmapVisibleHeight);
canvas.drawBitmap(mBitmap, mTempRect, bounds, mBitmapPaint);
}
} else if (mScaleType == ScaleType.SCALE_FIT) {
//bitmap scales to fit in bounds area
if (bounds.height() > 0 && bounds.height() > 0) {
float scale = Math.min((float)bounds.width()/mBitmap.getWidth(), (float)bounds.height()/mBitmap.getHeight());
float bitmapScaledWidth = scale * mBitmap.getWidth();
float bitmapScaledHeight = scale * mBitmap.getHeight();
int centerPadding = (int)(bounds.width()-bitmapScaledWidth)/2;
mTempRect.set(bounds.left + centerPadding, bounds.top, bounds.right - centerPadding, bounds.top+(int)bitmapScaledHeight);
canvas.drawBitmap(mBitmap, null, mTempRect, mBitmapPaint);
}
}
}
}
With this approach you are flexible to apply any scale logic that you need
Another approach would be to create patch 9 images of your regular image and have it stretch scale the way you want it to.
You could have it center the content by putting 9-patch-dots in the corners that will preserve your ratio obviously (assuming the outer most edge of your image is repeatable/transparent).
Hopefully you get the idea.
If your bitmap is wider than it is tall, use android:gravity="fill_vertical". Otherwise, use android:gravity="fill_horizontal". This has a similar effect as using android:scaleType="centerCrop" on an ImageView.
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="fill_vertical"
android:src="#drawable/image" />
If you support multiple orientations, you can create one bitmap XML file in the drawable-port folder and the other in the drawable-land folder.
Using the method described by a.ch worked great for me except I used this scale type which worked much better for what I needed:
android:scaleType="centerCrop"
Here is a full list of available scale types:
http://developer.android.com/reference/android/widget/ImageView.ScaleType.html
Try using InsetDrawable (worked well for me).
Just give this your drawable, and insets (or padding) you want from either of the four sides.
InsetDrawable insetDrawable = new InsetDrawable(drawable, insetLeft, insetTop, insetRight, insetBottom);
It is specifically used for setting background drawable, of size smaller or than the View.
See Here :
https://developer.android.com/reference/android/graphics/drawable/InsetDrawable.html
Old question, but none of the other answers worked for me. This xml code did however:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:scaleType="centerCrop"
android:src="#drawable/background_image"/>
</RelativeLayout>
In order to fit the image to the available space (or if you have set width and height in dp), I have tried the following approach, also if the image is not too wide.
Here I have set same width and height for square images [or you can wrap_content on both].
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentTop="true"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="#drawable/background_image"/>
</RelativeLayout>
adjust view bounds and scale type center fit does the trick.
I found this great thread describing how to "eat the cake and have it too", i.e. use image for a Button instead of ImageButton (which doesn't allow SetText(), resizing, etc.).
This is achieved by using the View attribute:
android:background="#drawable/bgimage"
The only problem with this is that it stretches the image to fit the button size.
Short of hard-coding a fixed button size (in pixels!), is there a way to tell Android not to stretch the background image at all and either crop or pad it?
You can create an xml bitmap and use it as background for the view. To prevent stretching you can specify android:gravity attribute.
for example:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/dvdr"
android:tileMode="disabled" android:gravity="top" >
</bitmap>
There are a lot of options you can use to customize the rendering of the image
http://developer.android.com/guide/topics/resources/drawable-resource.html#Bitmap
You should use ImageView if you don't want it to stretch.
Background images will always stretch to fit the view.
You need to set it as a Drawable to force the image aspect to the object.
Otherwise, if you are sticking with the Button idea, then you will need to force the scaling in the button to prevent the image from stretching.
Code:
onCreate(Bundle bundle) {
// Set content layout, etc up here
// Now adjust button sizes
Button b = (Button) findViewById(R.id.somebutton);
int someDimension = 50; //50pixels
b.setWidth(someDimension);
b.setHeight(someDimension);
}
Simply using ImageButton instead of Button fixes the problem.
<ImageButton android:layout_width="30dp"
android:layout_height="30dp"
android:src="#drawable/bgimage" />
and you can set
android:background="#null"
to remove button background if you want.
Quick Fix !! :-)
I am using an ImageView in an RelativeLayout that overlays with my normal layout. No code required.
It sizes the image to the full height of the screen (or any other layout you use) and then crops the picture left and right to fit the width. In my case, if the user turns the screen, the picture may be a tiny bit too small. Therefore I use match_parent, which will make the image stretch in width if too small.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/main_backgroundImage"
android:layout_width="match_parent"
//comment: Stretches picture in the width if too small. Use "wrap_content" does not stretch, but leaves space
android:layout_height="match_parent"
//in my case I always want the height filled
android:layout_alignParentTop="true"
android:scaleType="centerCrop"
//will crop picture left and right, so it fits in height and keeps aspect ratio
android:contentDescription="#string/image"
android:src="#drawable/your_image" />
<LinearLayout
android:id="#+id/main_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</LinearLayout>
</RelativeLayout>
I had the same problem: you should only use a 9-patch image (.9.png) instead of your original picture.
Serge
Use draw9patch... included within Android Studio's SDK tools. You can define the stretchable areas of your image. Important parts are constrained and the image doesn't look all warped. A good demo on dra9patch is HERE
Use draw9patch to change your existing splash.png into new_splash.9.png,
drag new_splash.9.png into the drawable-hdpi project folder
ensure the AndroidManifest and styles.xml are proper as below:
AndroidManifest.xml:
<application
...
android:theme="#style/splashScreenStyle"
>
styles.xml:
<style name="splashScreenStyle" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowBackground">#drawable/new_splash</item>
</style>
I had a background image, not big in size, but with weird dimensions - therefore the stretching and bad performance. I made a method with parameters Context, a View and a drawable ID(int) that will match the device screen size. Use this in e.g a Fragments onCreateView to set the background.
public void setBackground(Context context, View view, int drawableId){
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),drawableId);
bitmap = Bitmap.createScaledBitmap(bitmap, Resources.getSystem().
getDisplayMetrics().widthPixels,
Resources.getSystem().getDisplayMetrics().heightPixels,
true);
BitmapDrawable bitmapDrawable = new BitmapDrawable(context.getResources(),
bitmap);
view.setBackground(bitmapDrawable);
}
Here's a version of Santosh's answer for programmatically-created buttons, without the need for a separate XML configuration:
Button button = new Button(getContext());
Bitmap backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.my_button);
BitmapDrawable backgroundDrawable = new BitmapDrawable(getResources(), backgroundBitmap);
backgroundDrawable.setGravity(Gravity.CENTER); // also LEFT, CENTER_VERTICAL, etc.
backgroundDrawable.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP));
button.setBackground(backgroundDrawable);
I included the ColorFilter line since that works a little differently from buttons with a normal background image.
You can use a FrameLayout with an ImageView as the first child, then your normal layout as the second child:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/background_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="#drawable/your_drawable"/>
<LinearLayout
android:id="#+id/your_actual_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</FrameLayout>
The key is to set the drawable as the image of the button, not as a background. Like this:
rb.setButtonDrawable(R.drawable.whatever_drawable);
One can use a plain ImageView in his xml and make it clickable
(android:clickable="true")?
You only have to use as src an image that has been shaped like a button i.e round corners.
I have a layout with two images:
one that should strech to the screen width
one above it that should scale to the same proportion the first one was automaticaly scaled (relative to the original image size)
More specific: the two images are slices of the same image, and therefore some details inside them should match.
Can I make this in XML?
If I cannot do it through XML, maybe I could prescale the graphics. In this case, how should I prescale them?
This is a bit of a hack, but it would allow you to do this in xml.
If you know that, for example, the top image is X% of the size of the bottom one, then you can use LinearLayout's layout_weight to position and size the top image in terms of percentage of the screen:
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView android:id="#+id/left_filler" android:layout_weight="20"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<ImageView android:id="#+id/top_image" android:layout_weight="50"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<ImageView android:id="#+id/right_filler" android:layout_weight="30"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</LinearLayout>
... bottom image
The above would size top_image at 50% of the screen with an offset of 20% from the left. As long as top_image is 50% the size of bottom_image, this will keep similar scale.
Alternatively, the "right" way to do this is probably to override onDraw() in a custom view and use canvas drawing methods.
You could use the Canvas class method drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
for drawing the specified bitmap by scaling/translating automatically to fill the destination rectangle. This can be used for both the bitmaps with different Rect. The Rect can be formulated by dividing the current width and height of the layout. So that the program will scale the images in accordance with devices having different screen size.