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.
Related
Background
I have 2 imageViews, one on top of another, that have an animation together . I need them to blend with each other, using the "Multiply" effect, during this animation.
Something similar to this question which is about colors, yet with images (VectorDrawable in my case, but I can use PNG instead if needed).
Here's a sketch to demonstrate what I'd like to do:
Notice that the part of the images that overlap is darker than the original color of the arrows.
The problem
So far I can't find a way to do it. I know of course how to put a view on top of another, but changing the color of a part of the bitmap, based on the other, is something I can't find.
What I've found
I tried to use :
imageView.getDrawable().setColorFilter(..., PorterDuff.Mode.MULTIPLY)
for both the ImageViews, but it doesn't seem to work. What it does is actually change the entire color of the imageView, merging with the color I provide as the parameter.
It makes sense, because it's just a colorFilter, similar to tint.
I also tried alpha for each of the ImageViews, but this also mean that a part of the ImageViews (the parts that don't overlap) will have a semi-transparent color.
I could theoretically get the bitmap of each of them, and then perform the filter on the result, but this is not practical as I need to show it during animation between the two.
The question
How can I blend the 2 imageViews ?
Since you're using VectorDrawable, Have you considered animating the drawable rather than the view?
https://developer.android.com/reference/android/graphics/drawable/AnimatedVectorDrawable.html
try this:
public Bitmap combineImages(Bitmap frame, Bitmap image) {
Bitmap cs = null;
Bitmap rs = null;
rs = Bitmap.createScaledBitmap(frame, image.getWidth(),
image.getHeight(), true);
cs = Bitmap.createBitmap(rs.getWidth(), rs.getHeight(),
Bitmap.Config.RGB_565);
Canvas comboImage = new Canvas(cs);
comboImage.drawBitmap(image, 0, 0, null);
comboImage.drawBitmap(rs, 0, 0, null);
if (rs != null) {
rs.recycle();
rs = null;
}
Runtime.getRuntime().gc();
return cs;
}
OK, it seems it's probably not possible using 2 views, because of the way they work on Android, so instead, I want to show how to do it with a LayerDrawable instead:
public class LD extends LayerDrawable {
private Paint p = new Paint();
public LD(Drawable[] layers) {
super(layers);
p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
int count = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
for (int i = 0, numberOfLayers = getNumberOfLayers(); i < numberOfLayers; ++i) {
this.getDrawable(i).draw(canvas);
canvas.saveLayer(null, p, Canvas.ALL_SAVE_FLAG);
}
canvas.restoreToCount(count);
//original code:
//int count = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
//this.getDrawable(0).draw(canvas);
//canvas.saveLayer(null, p, Canvas.ALL_SAVE_FLAG);
//this.getDrawable(1).draw(canvas);
//canvas.restoreToCount(count);
}
}
Usage:
ImageView imageView = (ImageView) findViewById(R.id.imageView);
Drawable drawable1 = AppCompatResources.getDrawable(this, R.drawable....);
Drawable drawable2 = AppCompatResources.getDrawable(this, R.drawable....);
Drawable drawable3 = AppCompatResources.getDrawable(this, R.drawable....);
LD layerDrawable = new LD(new Drawable[]{
drawable1,
drawable2,
drawable3
});
imageView.setImageDrawable(layerDrawable);
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.
I am using a Drag and Drop event, and I want to change the drag shadow's opacity. I extended View.DragShadowBuilder class but I'm not able to set drag shadow's opacity. By default, it's semi transparent. I want it to look solid with no transparency. I do this:
#Override
public void onDrawShadow(Canvas canvas) {
ImageView ivPiece = (ImageView) getView();
BitmapDrawable drawable = (BitmapDrawable) ivPiece.getDrawable();
Bitmap piece = drawable.getBitmap();
Paint paint = new Paint();
paint.setAlpha(255);
paint.setAntiAlias(true);
canvas.drawBitmap(piece, 0, 0, paint);
}
But it doesn't work.
It appears that this isn't possible directly. However, an answer to a similar question gives a work-around, using a drag container. Hopefully that is helpful to you!
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);
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?