XML Drawable consisting of two images - android

I am trying to create a XML Drawable which I would use instead of the default marker in OSMDroid.
This is what it should look like in the end:
The black part will be loaded while creating the marker, as every marker will have a different image there. (these will be loaded from a Database if that´s important)
I tried to create a XML Drawable, and it kinda works, but the black part seems to be scaled to fit the image, thus making the marker just a big black square. (without the black part it works fine)
My current XML:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="#drawable/marker"/> // main
<item android:drawable="#drawable/markerfill" /> // black part
</layer-list>
I tried using a scale for the second item, but as I looked into scales, it looked like I can´t use scales for this.
What I want:
I load the black box into the "main" part, resize it if necessary (while keeping the proportions) and change it from Java-Code.
I will be using this marker for OSMDroid.
What would be the best approach for this?

What I want: I load the black box into the "main" part, resize it if
necessary (while keeping the proportions) and change it from
Java-Code. I will be using this marker for OSMDroid.
If the first layer image will not be resized/scaled when used(I don't know how OSMDroid manages that marker) then you could use the current approach of a LayerDrawable and "place" the dynamic image with LayerDrawable.setLayerInset():
public Drawable makeBasicMarker(Bitmap bitmap) {
Drawable[] layers = new Drawable[2];
layers[0] = new BitmapDrawable(getResources(),
BitmapFactory.decodeResource(getResources(), R.drawable.marker));
layers[1] = new BitmapDrawable(getResources(), bitmap);
LayerDrawable ld = new LayerDrawable(layers);
ld.setLayerInset(1, xx, xx, xx, xx); // xx would be the values needed so bitmap ends in the upper part of the image
return ld;
}
If the image is scaled then you need to make your own Drawable and draw the parts yourself placing them appropriately.

give it a try like this: 1.define a view with the first drawable; 2. draw the second item with drawBitmap() method 3. call the postInvalidate() method to redraw.

drawing mBar on mBG, referring to those:
void onDraw(final Canvas canvas) {
super.onDraw(canvas);
float zoomX = mWidth / (float) mBG.getWidth();
float zoomY = mHeight / (float) mBG.getHeight();
canvas.drawBitmap(mBG, 0, 0, null);
canvas.drawBitmap(mBG, new Rect(0, 0, mBG.getWidth(), mBG.getHeight()),
new RectF(0, 0, mWidth, mHeight), null);
....
canvas.drawBitmap(mBar, new Rect(0, 0, (int) w, (int) h), new RectF(
X_LOCATION * zoomX, Y_LOCATION * zoomY, (X_LOCATION + w)
* zoomX, (Y_LOCATION + h) * zoomY), null);
}

Related

How to have a FAB with option of no padding, yet still have it elevation?

background
I have a small circular contact photo view that I need to show the user.
If the photo is available, I should show it, while the image gets rounded to a circle, and has an elevation.
If the photo isn't available, I show an icon inside with a background, while still it's rounded and has elevation.
The problem
I've managed to make the photo showing work only on Android 5 and above:
But, it has a bad color around the edges as it tries to show the background of the FAB, and on Android 4.x and below it it showing as a simple rectangular content, with nothing that's related to FAB, probably because padding is what's protecting it for the shadow to be shown.
I also need to be able to add a stroke (a thin line of specific color around the rounded image), but I'm not sure how to add it.
What I've tried
This is in the layout file:
<android.support.design.widget.FloatingActionButton
android:id="#+id/image"
android:layout_width="#dimen/test"
android:layout_height="#dimen/test"/>
"test" is 80dp.
And this is in the code:
FloatingActionButton floatingActionButton = (FloatingActionButton) view.findViewById(R.id.image);
floatingActionButton.setPadding(0, 0, 0, 0);
final Bitmap bitmap = ...
RoundedCornersDrawable roundedCornersDrawable = new RoundedCornersDrawable(getResources(), bitmap, bitmap.getWidth() / 2);
floatingActionButton.setImageDrawable(roundedCornersDrawable);
code of RoundedCornersDrawable:
public class RoundedCornersDrawable extends BitmapDrawable {
private final BitmapShader bitmapShader;
private final Paint p;
private final RectF rect;
private final float borderRadius;
public RoundedCornersDrawable(final Resources resources, final Bitmap bitmap, final float borderRadius) {
super(resources, bitmap);
bitmapShader = new BitmapShader(getBitmap(), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
final Bitmap b = getBitmap();
p = getPaint();
p.setAntiAlias(true);
p.setShader(bitmapShader);
final int w = b.getWidth(), h = b.getHeight();
rect = new RectF(0, 0, w, h);
this.borderRadius = borderRadius < 0 ? 0.15f * Math.min(w, h) : borderRadius;
}
#Override
public void draw(final Canvas canvas) {
canvas.drawRoundRect(rect, borderRadius, borderRadius, p);
}
}
The question
How do I get the FAB to work this way? How can I disable the padding at will, for all Android versions, and yet still have a rounded image with shadow ?
How do I also add a stroke to the rounded FAB ?
I think I was too fast in writing this post, while it seems there is already one about it here:
How to add a shadow and a border on circular imageView android?
I will have a look at it and see if it matches the requirements of what I need.
EDIT: it doesn't look as well as FAB (especially the shadow), plus I don't see the ability to put the small content in the center, like in FAB. It always puts the content in center-crop way.
Of course, I could just put a smaller sized image in it....
EDIT: I think I've found the right library this time:
https://github.com/lopspower/CircularImageView
It allows a shadow, customize the shadow size and color, and also a border, and also how to scale the image.
It's not quite a FAB, and it doesn't have the ability to put the image at the center without cropping, but it's enough for me.

How to combine shape drawable and drawable into single drawable

I am generating shape drawable and drawable runtime in android programmatically. What all I need is to combine two drawable into single drawable. I tried to implement through following methods, but nothing seems to work out.
Sample code to combine two drawables into single using LayerDrawable
public static LayerDrawable drawCircleWithIcon (Context context, int width, int height, int color,Drawable drawable) {
ShapeDrawable oval = new ShapeDrawable (new OvalShape ());
oval.setIntrinsicHeight (height);
oval.setIntrinsicWidth (width);
oval.getPaint ().setColor (color);
Drawable[] layers = new Drawable[2];
layers[0] = drawable;
layers[1] = oval;
LayerDrawable composite1 = new LayerDrawable (layers);
return composite1;
}
Arguments I am passing:
width - width of the circle
height - height of the circle
color - color of the circle
drawable - icon that needs to be fit inside the ShapeDrawable (i.e. Round circle inside placed with icon)
My requirement:
I need to combine two drawables (one is ShapeDrawable and drawable). The output must be like as following
Kindly please help me with your solutions or alternate methods to merge two drawable into one drawable icon. Thanks in advance.
You can try using an imageview an set the properties backgroundDrawable and imageDrawable or imageResource.
yourImageView.setBackgroundDrawable(yourShape);
YourImageView.setImageDrawable(yourImage);
To adjust the drawable and keep aspect ratio try using, his methods.
yourImageView.setAdjustViewBounds(true);
yourImageView.setScaleType(ScaleType.FIT_CENTER);
Due to this answer does't adjust to the question, if what you want is to create a single image combining multiple drawables, you can try something like this:
Resources res = getResources();
// Front image
Bitmap image = BitmapFactory.decodeResource(res, R.drawable.frontImage);
// The rounded background
Bitmap background = BitmapFactory.decodeResource(res, shapeDrawable);
RoundedBitmapDrawable dr = RoundedBitmapDrawableFactory.create(res, background);
dr.setCornerRadius(Math.max(background.getWidth(), background.getHeight()) / 2.0f);
Bitmap combined = Bitmap.createBitmap(dr.getWidth(), dr.getHeight(), Bitmap.Config.ARGB_8888);
// Creates the combined drawable
Canvas canvas = new Canvas(combined);
canvas.drawBitmap(dr,0, 0, null);
canvas.drawBitmap(image, dr.getWidht() / 2.0f, dr.getHeight() / 2.0f, null);
I have not tried the code above, but i'm showing something could help you.

Android how to apply mask on ImageView?

So I tried the code from here: Creating an ImageView with a mask. I'm using the following images as original and mask:
However, the result I get is this:
Note that the window background is not black, but holo light (which on the galaxy nexus looks like a very pale gray, not completely white). The second image is the result I get when an item is selected on a list view.
If instead I create a new Bitmap using the same algorithm and then pass it to the image view instead of overriding onDraw(), it draws correctly:
Canvas canvas = new Canvas();
Bitmap mainImage = //get original image
Bitmap maskImage = //get mask image
Bitmap result = Bitmap.createBitmap(mainImage.getWidth(), mainImage.getHeight(), Bitmap.Config.ARGB_8888);
canvas.setBitmap(result);
Paint paint = new Paint();
paint.setFilterBitmap(false);
canvas.drawBitmap(mainImage, 0, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(maskImage, 0, 0, paint);
paint.setXfermode(null);
imageView.setImageBitmap(result);
I get the expected result:
Note the fade is correctly applied. This is more evident when a selection is made.
So what's going on on ImageView's onDraw method to create this black backdrop instead of letting the window background show through? What's interesting is that if the original image itself has some transparency, that transparency is respected, for example:
I can't figure it out by myself. I'd rather be able to do it on onDraw instead of pre-creating the bitmap because it only works for bitmaps as source and mask. I want to be able to do it with other drawables like gradients and solid colours but on those cases the width and height are not set.
I have found the perfect combination for creating masking without black border after researching through all the stackoverflow posts. It suits my purpose quite well.
Currently I'm creating a draggable view using one normal image and a masking image (a png with transparency), so I'll need to override the onDraw function.
private Bitmap mImage = ...;
private Bitmap mMask = ...; // png mask with transparency
private int mPosX = 0;
private int mPosY = 0;
private final Paint maskPaint;
private final Paint imagePaint;
public CustomView (final Context context) {
maskPaint = new Paint();
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
imagePaint = new Paint();
imagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
}
/* TODO
if you have more constructors, make sure you initialize maskPaint and imagePaint
Declaring these as final means that all your constructors have to initialize them.
Failure to do so = your code won't compile.
*/
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.drawBitmap(mMask, 0, 0, maskPaint);
canvas.drawBitmap(mImage, mPosX, mPosY, imagePaint);
canvas.restore();
}
Answering my own question. The Xfermode was working as intended. The paint was making the resulting are of the canvas transparent (which was the canvas used by the window activity). Since the canvas itself was being set transparent, the window was showing what was behind it: the black background.
To do it properly, indeed a new Bitmap has to be created to hold the result of the alpha mask. I updated the code to take into account drawables of all types.
In this Code Apply:
mask_over = BitmapFactory.decodeResource(
getResources(), mask_over1[0]);
icon = Bitmap.createScaledBitmap(icon, screenwidth, screenwidth, false);
mask_over = Bitmap.createScaledBitmap(mask_over, screenwidth, screenwidth, false);
back_img=createBitmap_ScriptIntrinsicBlur(Bitmap.createScaledBitmap(cropview.croppedImage, screenwidth, screenwidth, false),25.0f);
LinearLayout.LayoutParams layoutParams111 = new LinearLayout.LayoutParams(screenwidth, screenwidth);

Android: Drawing tiled bitmaps with bottom or some other alignments similar to css background-position

I want to set a background of a View with a tiled bitmap, but the tiling needs to be anchored to the bottom-left, instead of the top-left corner (the default). For example, if the tiles are the smiley faces below, I want it to be tiled like:
Using xml drawables I could achieve either tiling (using tileMode="repeat") or bottom positioning (using gravity="bottom"), but combining both is not possible, even the documentation says so:
android:tileMode
Keyword. Defines the tile mode. When the tile mode is
enabled, the bitmap is repeated. Gravity is ignored when the tile mode
is enabled.
Although it's not internally supported, is there any way to achieve this, perhaps using custom views?
Another way would be to extend BitmapDrawable and override the paint() method:
In this method we avoid creating a new bitmap having the size of the view.
class MyBitmapDrawable extends BitmapDrawable {
private Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
private boolean mRebuildShader = true;
private Matrix mMatrix = new Matrix();
#Override
public void draw(Canvas canvas) {
Bitmap bitmap = getBitmap();
if (bitmap == null) {
return;
}
if (mRebuildShader) {
mPaint.setShader(new BitmapShader(bitmap, TileMode.REPEAT, TileMode.REPEAT));
mRebuildShader = false;
}
// Translate down by the remainder
mMatrix.setTranslate(0, getBounds().bottom % getIntrinsicHeight());
canvas.save();
canvas.setMatrix(mMatrix);
canvas.drawRect(getBounds(), mPaint);
canvas.restore();
}
}
It can be set to the view like this:
view.setBackgroundDrawable(new MyBitmapDrawable(getResources().getDrawable(R.drawable.smiley).getBitmap()));
Just a thought, and it's pretty roundabout, but could you flip your image vertically, and then apply a transform to your background to flip that vertically as well?
Using a custom view might involve handling all the drawing yourself, not just the background image.
Instead, I propose to set the view's background programmatically as shown:
// This drawable refers to an image directly and NOT an XML
BitmapDrawable smiley = (BitmapDrawable) getResources().getDrawable(R.drawable.smiley);
// Create a new bitmap with the size of the view
Bitmap bgBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bgBitmap);
// Translate down by the remainder
Matrix matrix = new Matrix();
matrix.setTranslate(0, view.getHeight() % smiley.getIntrinsicHeight());
canvas.setMatrix(matrix);
// Tile the smileys
Paint paint = new Paint();
paint.setShader(new BitmapShader(smiley.getBitmap(), TileMode.REPEAT, TileMode.REPEAT));
canvas.drawPaint(paint);
view.setBackgroundDrawable(new BitmapDrawable(bgBitmap));
Points to consider:
I'm not sure if view.getWidth() & view.getHeight() are the correct
methods to get the dimensions.
What if smiley size is bigger than the view?

ImageView with rounded corners and inner shadow

I need to make a thumbnail view with rounded corners and inner shadow. Usually I'm making ImageView frames with 9patches, which have served me well so far, but this time the effect I need requires drawing the inner shadow on top of the image (and not just around it). This lead me to extend the ImageView class and override the onDraw() method.
public class ThumbnailImageView extends ImageView {
After many tutorials (thanks StackOverflow!), I ended up with this code for the onDraw() method:
#Override
protected void onDraw(Canvas canvas) {
if (mBitmap == null) {
return;
}
int radius = 4;
int padding = 2;
int bleed = 2;
RectF frame = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xFF000000);
canvas.drawRoundRect(frame, radius, radius, mPaint);
Shader bitmapShader = new BitmapShader(mBitmap, TileMode.CLAMP, TileMode.CLAMP);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xFF000000);
mPaint.setMaskFilter(new BlurMaskFilter(bleed, Blur.INNER));
mPaint.setShader(bitmapShader);
canvas.drawRoundRect(frame, radius, radius, mPaint);
}
What I'm basically doing, is drawing a black rounded rectangle first and then drawing a rounded-corners bitmap with fading edges (with the BlurMaskFilter) on top of it. The result is what I want:
The mBitmap value is initialized in the ImageView constructor like this:
mDrawable = getDrawable();
if (mDrawable != null) {
mBitmap = ((BitmapDrawable) mDrawable).getBitmap();
}
The problem is that I am overriding onDraw() completely (no super.onDraw()) is called, so I have to pre-scale all images to the desired thumbnail size (e.g. 96x96) or else only the top-left corner of the image is drawn. What I want to be able to do is take advantage of all the scaling the framework is doing when I assign the following xml values to the ThumbnailImageView:
android:id="#+id/thumb"
android:layout_width="96dp"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
To do this, I thought I should somehow call super.onDraw() while getting the effects I need at the same time. I have managed to get the rounded rectange by adding a clipping path to the canvas, but I can't find a way to add the inner shadow. This is the new onDraw() code:
#Override
protected void onDraw(Canvas canvas) {
int radius = 4;
int padding = 4;
RectF frame = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);
Path clipPath = new Path();
clipPath.addRoundRect(frame, radius, radius, Path.Direction.CW);
canvas.clipPath(clipPath);
super.onDraw(canvas);
// add inner shadow
}
I can see two alternatives:
1) To properly pre-scale the ImageView's bitmap. But where is the best place to do it? In it's constructor? In the onDraw() method where the framework seems to be doing it? Is the framework even resizing any bitmap or is there another way to draw a scaled image on the canvas without being bad for performance?
2) To add the inner shadow layer on top of what the super.onDraw() is drawing so far, but I'm running out of ideas on how to do this.
Any help would be appreciated.
Thanks!
Take a look at Eric's (from squareup) presentation material from Oreilly's AndoridOpen Conference last year in his lecture titled Beautiful Android
It has a ton of info that should help you out.
I wish they had the video of his presentation somewhere. I could not find it. So sorry.
EDIT : Thanks to #mykola for the yt link

Categories

Resources