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.
Related
I want to create a drawable that contains a circle with a background color that comes from an external file. As such I unfortunately can't simply load the drawable from an Xml file but have to create it dynamically in Java.
How do I create my circle directly in Java?
You can use ShapeDrawables:
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
shapeDrawable.setIntrinsicHeight(height);
shapeDrawable.setIntrinsicWidth(width);
For circle, just use the same height and width.
I finally found a nonhacky way to create an oval shaped drawable:
GradientDrawable gd = new GradientDrawable();
int fillColor = Color.parseColor("FF0000");
gd.setColor(fillColor);
int strokeWidth = 2; // px not dp
int strokeColor = Color.parseColor("#000000");
gd.setStroke(strokeWidth, strokeColor);
gd.setShape(GradientDrawable.OVAL);
I am making a custom button view that has a gradient border, rounded corners and a transparant background. I am setting the background of the button to the drawable generated by the following code:
protected Drawable getBackgroundDrawable() {
GradientDrawable backgroundDrawable = new GradientDrawable(
mOrientation,
mGradientColors);
backgroundDrawable.setCornerRadius(getHeight() / 2);
backgroundDrawable.setShape(GradientDrawable.RECTANGLE);
if (!mFilled) {
Bitmap background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas backgroundCanvas = new Canvas(background);
backgroundCanvas.drawARGB(0, 0, 0, 0);
backgroundDrawable.setBounds(0, 0, getWidth(), getHeight());
backgroundDrawable.draw(backgroundCanvas);
Paint rectPaint = new Paint();
rectPaint.setAntiAlias(true);
rectPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
backgroundCanvas.drawRoundRect(new RectF(mStroke, mStroke,
getWidth() - mStroke,
getHeight() - mStroke),
getHeight() / 2,
getHeight() / 2,
rectPaint);
return new BitmapDrawable(getResources(), background);
} else {
return backgroundDrawable;
}
}
The only problem is, when you click the button now, you have no feedback. How can I add a ripple effect at the back of the button when I already use the generated drawable as a background? Do I have to do something with a LayerDrawable?
My button looks like this:
What I really want to achieve
Create a Drawable programmatically that has the same result as this XML drawable, where the item in the ripple element is my generated Drawable in the image above:
<ripple android:color="#android:color/black" xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/background"></item>
</ripple>
What I have tried
I now tried to make a RippleDrawable in combination with a GradientDrawable to recreate the XML above in code. Very easy, I thought:
GradientDrawable gradientDrawable = new GradientDrawable();
gradientDrawable.setColor(Color.TRANSPARENT);
gradientDrawable.setStroke(mStroke, Color.GREEN);
gradientDrawable.setCornerRadius(getHeight() / 2);
ColorStateList rippleStateList = ColorStateList.valueOf(Color.BLUE);
return new RippleDrawable(rippleStateList, gradientDrawable, gradientDrawable);
This returns a simple drawable with a green stroke. However, when you click it, nothing happens. Unlike the XML where you clearly can see a ripple effect over the border. When I set the third parameter to null, same effect. When I only set the second parameter to null it only returned an empty transparant drawable without any ripple effects.
You should use a RippleDrawable as the background of your custom button. The RippleDrawable has a mask layer, which is what you are looking for. To do this programmatically, you can create a RippleDrawable like so:
float[] outerRadii = new float[8];
Arrays.fill(outerRadii, height / 2);
RoundRectShape shape = new RoundRectShape(outerRadii, null, null);
ShapeDrawable mask = new ShapeDrawable(shape);
ColorStateList stateList = ColorStateList.valueof(ContextCompat.getColor(getContext(), R.color.ripple));
setBackground(new RippleDrawable(stateList, getBackgroundDrawable(), mask);
Keep in mind that RippleDrawable is only available for API Level 21+, so if you are looking to support older versions as well you will need to fall back to something else; possibly android.R.attr.selectableItemBackground or a StateListDrawable, which will give your button a basic "pressed" background for older platform versions.
I'm attempting to colorize a graphic using setColorFilter. The following code seems to work fine on lollipop, but it seems to have no effect on kitkat, the icon is rendered in it's original colors:
Drawable icon = ContextCompat.getDrawable(context, R.drawable.ic_chat_button).mutate();
icon.setColorFilter(context.getResources().getColor(R.color.control_tint_color), PorterDuff.Mode.SRC_ATOP);
icon.invalidateSelf();
The mutate and invalidateSelf calls don't seem to have any effect on the problem here, just leaving them in as an example of part of what's been tried to figure out what's going on.
FWIW, I'm using the drawable as part of a LayerDrawable in a StateListDrawable that gets used as either the background for a button or as the drawable for an ImageView The results are consistent (ie., wrong on kitkat) either way. I've also tried putting the icon drawable directly into the StateListDrawable again with no change in behavior. In all cases, it works fine on lollipop, but doesn't work on kitkat.
As an experiment, I took the tinted Drawable out of the StateListDrawable but not the LayerDrawable and it works as expected. Apparently there's something flawed in KitKat's implementation of StateListDrawable that prevents it from working, that has been remedied in later versions.
Ultimately, it seems like the problem is that KitKat doesn't support using a ColorFilter (or implicitly an alpha) on a Drawable that will in turn be in a StateListDrawable. My solution was to use the same to code to construct the complex Drawable and then render that into a simple BitMapDrawable:
static Drawable createDrawable(Context context, int color, boolean disabled) {
OvalShape oShape = new OvalShape();
ShapeDrawable background = new ShapeDrawable(oShape);
background.getPaint().setColor(color);
ShapeDrawable shader = new ShapeDrawable(oShape);
shader.setShaderFactory(new ShapeDrawable.ShaderFactory() {
#Override
public Shader resize(int width, int height) {
return new LinearGradient(0, 0, 0, height,
new int[]{
Color.WHITE,
Color.GRAY,
Color.DKGRAY,
Color.BLACK
}, null, Shader.TileMode.REPEAT);
}
});
Drawable icon = ContextCompat.getDrawable(context, R.drawable.ic_chat_button).mutate();
icon.setColorFilter(context.getResources().getColor(R.color.control_tint_color), PorterDuff.Mode.SRC_IN);
Drawable layer = new LayerDrawable(new Drawable[]{ shader, background, icon });
layer.setAlpha(disabled ? 128 : 255);
// Note that on KitKat, setting a ColorFilter on a Drawable contained in a StateListDrawable
// apparently doesn't work, although it does on later versions, so we have to render the colored
// bitmap into a BitmapDrawable and then put that into the StateListDrawable
Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
layer.setBounds(0, 0, layer.getIntrinsicWidth(), layer.getIntrinsicHeight());
layer.draw(canvas);
return new BitmapDrawable(context.getResources(), bitmap);
}
Rather than attach the coloring to something like "disabled" state (as in the accepted answer) I found the answer to be simpler by focusing on recoloring and let my usage leverage how to include the now-tinted image in the StateListDrawable. (And FYI, I've tried to translate from the Xamarin C# I'm using, but the below code may not complie correctly as Java)
static Drawable recolorDrawable(Drawable icon, int toColor)
{
Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas myCanvas = new Canvas(bitmap);
icon.setColorFilter(toColor, PorterDuff.Mode.SRC_IN);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
icon.draw(myCanvas);
return new BitmapDrawable(context.getResources(), bitmap);
}
Finally, in all fairness to the accepted answer, I'm rather foreign to Android development and can thank him for showing the pieces I needed before then simplifying them.
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);
}
How can I create a selectable circular ImageView like in the current Google+ app used for the profile pictures?
This is what I refer to:
The image above is unselected and the below is selected.
I try to replicate the profile pictures 1 to 1.
My work so far:
loadedImage is the Bitmap which is displayed
mImageView.setBackground(createStateListDrawable());
mImageView.setImageBitmap(createRoundImage(loadedImage));
The used methods:
private Bitmap createRoundImage(Bitmap loadedImage) {
Bitmap circleBitmap = Bitmap.createBitmap(loadedImage.getWidth(), loadedImage.getHeight(), Bitmap.Config.ARGB_8888);
BitmapShader shader = new BitmapShader(loadedImage, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);
Canvas c = new Canvas(circleBitmap);
c.drawCircle(loadedImage.getWidth() / 2, loadedImage.getHeight() / 2, loadedImage.getWidth() / 2, paint);
return circleBitmap;
}
private StateListDrawable createStateListDrawable() {
StateListDrawable stateListDrawable = new StateListDrawable();
OvalShape ovalShape = new OvalShape();
ShapeDrawable shapeDrawable = new ShapeDrawable(ovalShape);
stateListDrawable.addState(new int[] { android.R.attr.state_pressed }, shapeDrawable);
stateListDrawable.addState(StateSet.WILD_CARD, shapeDrawable);
return stateListDrawable;
}
The size of the ImageView is imageSizePx and the size of the image is imageSizePx - 3. So, that means the background should overlap the image. Which doesn't work.
Really simple solution, thanks to #CommonsWare for the tips.
Size of Bitmap: imageSizePx - 3DP
Size of ImageView: imageSizePx
mImageView.setBackground(createStateListDrawable(imageSizePx));
mImageView.setImageBitmap(loadedImage);
private StateListDrawable createStateListDrawable(int size) {
StateListDrawable stateListDrawable = new StateListDrawable();
OvalShape ovalShape = new OvalShape();
ovalShape.resize(size, size);
ShapeDrawable shapeDrawable = new ShapeDrawable(ovalShape);
shapeDrawable.getPaint().setColor(getResources().getColor(R.color.somecolor));
stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, shapeDrawable);
stateListDrawable.addState(new int[]{android.R.attr.state_focused}, shapeDrawable);
stateListDrawable.addState(new int[]{}, null);
return stateListDrawable;
}
Assuming that by "selectable" you mean "checkable, like a CheckBox", then that could be some CompoundButton that is using a StateListDrawable with regular and checked states for the background behind the "SkillOverflow" foreground image.
You can use uiautomatorviewer to see what the actual widget is that Google+ uses.
You can make a custom View that extends ImageView. Override onDraw to clip the canvas with a circular region and draw the image on the canvas. Something like this as a starting point:
public class CircularImageView extends ImageView {
/* constructors omitted for brevity ... */
protected void onDraw(Canvas canvas) {
int saveCount = canvas.save();
// clip in a circle
// draw image
Drawable d = getDrawable();
if (d != null) {
d.draw(canvas);
}
// do extra drawing on canvas if pressed
canvas.restoreToCount(saveCount);
}
}
Take some time to get familiar with the Canvas and other 2D drawing APIs in Android.
I was able to achieve this effect through a custom ImageView by overriding onDraw and painting some sort of "border" whenever it detects a touch event. As a bonus, there is a selector overlay. Hopefully you may find this view helpful...
https://github.com/Pkmmte/CircularImageView
https://github.com/hdodenhof/CircleImageView
Best library so far, super easy to integrate. It will give you border width and color option, you can change the color onClick to match your query.