I newbie to Android Development, and Canvas drawings.
When I draw stuff on canvas, its easier for me to work on rectangle portion of width=height=1.
I try to scale according, but when trying to drawArc its draw it badly.
Can you please tell me what I'm doing wrong?
public class MyChart extends View{
private RectF dimentionRect;
private Paint dimentionPaint;
private static final String TAG = "VERBOSE";
public MyChart(Context context){
super(context);
initDrawingTools();
}
public MyChart(Context context,AttributeSet attrs) {
super(context,attrs);
initDrawingTools();
}
private void initDrawingTools(){
dimentionPaint = new Paint();
dimentionPaint.setColor(Color.GREEN);
dimentionPaint.setStyle(Style.FILL);
}
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int chosenDimention = Math.min(widthSize, heightSize);
setMeasuredDimension(chosenDimention, chosenDimention);
Log.v(TAG, "onMeasure: "+chosenDimention);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.v(TAG, "onSizeChange");
}
#Override
public void onDraw(Canvas canvas){
float width = (float)getWidth();
Log.v(TAG, "onDraw: "+width);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(width, width);
dimentionRect = new RectF(0,0,1f,1f);
//canvas.drawRect(dimentionRect, dimentionPaint); //
canvas.drawArc(dimentionRect, 0, 180, true, dimentionPaint);
// canvas.drawCircle(0.5f, 0.5f, 0.3f, dimentionPaint);
canvas.restore();
}
}
Ok, I didn't find a solution but I was able to find a work-around for it.
I created a background bitmap and using it for a local Canvas which do the drawing stuff. after all drawings I draw the bitmap again into my canvas.
I know its not elegant but thats the solution I found
private void drawBackground(Canvas canvas){
if(background != null){
canvas.drawBitmap(background, 0,0,backgroundPaint);
Log.e(TAG, "Drawing background");
}
}
private void regenerateBackground(){
if(background != null){
background.recycle();
}
background = Bitmap.createBitmap(getWidth(), getWidth(), Config.ARGB_8888);
RectF rect = new RectF(0,0,getWidth(),getWidth());
Canvas c = new Canvas(background);
c.drawRect(rect, backgroundPaint);
}
#Override
protected void onDraw(Canvas canvas){
drawBackground(canvas);
float width = (float)getWidth();
Canvas c = new Canvas(background);
c.save(Canvas.MATRIX_SAVE_FLAG);
c.scale(width,width);
border = new RectF(0,0,1,1);
//
Paint p = new Paint();
p.setColor(Color.GREEN);
p.setStyle(Style.FILL);
c.drawArc(border, 0, 360,true, p);
c.restore();
}
Related
I want to draw image inside circle with bitmap and fill color outside.
I have extended AppCompatImageView and override onDraw method, but it displays white circle inside canvas.
following is my class :
class ImageMagnifier2 extends AppCompatImageView {
public ImageMagnifier2(final Context context) {
super(context);
init();
}
#Override
public boolean dispatchTouchEvent(MotionEvent me) {
// Call onTouchEvent of SimpleGestureFilterLongPress class
return super.dispatchTouchEvent(me);
}
private void init() {
setWillNotDraw(false);
setLayerType(LAYER_TYPE_HARDWARE, null);
zoomPos = new PointF(0, 0);
matrix = new Matrix();
paint = new Paint();
paintBackground = new Paint();
paint.setColor(getResources().getColor(android.R.color.transparent));
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!zooming) {
buildDrawingCache();
} else {
bitmap = getDrawingCache();
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(shader);
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
paint.getShader().setLocalMatrix(matrix);
paintBackground.setColor(Color.parseColor("#F227aae0"));//F227aae0
paintBackground.setStyle(Paint.Style.FILL);
paintBackground.setAntiAlias(true);
Rect rectangle = new Rect( 0, 0, canvas.getWidth(), canvas.getHeight() );
canvas.drawRect(rectangle,paintBackground);
canvas.drawCircle(zoomPos.x, zoomPos.y, sizeOfMagnifier - 5, paint);
}
}
It working fine when draw bitmap without outside rectangle or canvas fill color. but i need to fill outside color with bitmap.
Thank you for help in advance.
The following is a code the I wrote previously when I had the same issues, You may want to use the following class:
public class CircleImageView extends AppCompatImageView {
private RectF drawableRect = new RectF();
private Matrix shaderMatrix = new Matrix();
private Paint bmpPaint = new Paint();
private Paint backgroundPaint = new Paint();
private Bitmap bmp;
private int bmpWidth, bmpHeight;
private BitmapShader bmpShader;
private float radius;
public CircleImageView(Context context) {
super(context);
setScaleType(ScaleType.CENTER_CROP);
backgroundPaint.setStyle(Paint.Style.FILL);
backgroundPaint.setColor(Color.TRANSPARENT);
init();
}
#Override
protected void onDraw(Canvas canvas) {
if (bmp == null) return;
canvas.drawPaint(backgroundPaint);
canvas.drawCircle(drawableRect.centerX(), drawableRect.centerY(), radius, bmpPaint);
}
private Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) return null;
if (drawable instanceof BitmapDrawable) return ((BitmapDrawable) drawable).getBitmap();
try {
Bitmap bitmap;
if (drawable instanceof ColorDrawable) bitmap = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
else bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private void initImage() {
bmp = getBitmapFromDrawable(getDrawable());
init();
}
private void init() {
if (getWidth() == 0 && getHeight() == 0) return;
if (bmp == null) {invalidate();return;}
bmpShader = new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
bmpPaint.setAntiAlias(true);
bmpPaint.setShader(bmpShader);
bmpHeight = bmp.getHeight();
bmpWidth = bmp.getWidth();
drawableRect.set(new RectF(0,0,getWidth(),getHeight()));
radius = Math.min(drawableRect.height() / 2.0f, drawableRect.width() / 2.0f);
updateMatrix();
invalidate();
}
private void updateMatrix() {
float scale;
float dx = 0;
float dy = 0;
shaderMatrix.set(null);
if (bmpWidth * drawableRect.height() > drawableRect.width() * bmpHeight) {
scale = drawableRect.height() / (float) bmpHeight;
dx = (drawableRect.width() - bmpWidth * scale) * 0.5f;
} else {
scale = drawableRect.width() / (float) bmpWidth;
dy = (drawableRect.height() - bmpHeight * scale) * 0.5f;
}
shaderMatrix.setScale(scale, scale);
shaderMatrix.postTranslate((int) (dx + 0.5f) + drawableRect.left, (int) (dy + 0.5f) + drawableRect.top);
bmpShader.setLocalMatrix(shaderMatrix);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
init();
}
public void setBackgroundColor(#ColorInt int backgroundColor) {
backgroundPaint.setColor(backgroundColor);
invalidate();
}
#Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
initImage();
}
#Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
initImage();
}
#Override
public void setImageResource(#DrawableRes int resId) {
super.setImageResource(resId);
initImage();
}
#Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
initImage();
}
/* if this answer solved your problem please mark it as accepted by clicking the check mark next to the answer. Thank you */
}
Note: I know there are a lot of questions and repositories about this, but none seems to fit what I try to achieve.
Background
Given a bitmap of any aspect-ratio, I wish to set it as the content of an ImageView (using a drawable only, without extending the ImageView), so that the content will be center-cropped, and yet in the shape of a circle.
All of this, with minimal memory usage, because the images could be quite large sometimes. I do not want to create a whole new Bitmap just for this. The content is already there...
The problem
All solutions I've found lack one of the things I've written: some do not center-crop, some assume the image is square-shaped, some create a new bitmap from the given bitmap...
What I've tried
Other than trying various repositories, I've tried this tutorial, and I tried to fix it for the case of non-square aspect ratios, but I've failed.
Here's its code, in case the website will get closed:
public class RoundImage extends Drawable {
private final Bitmap mBitmap;
private final Paint mPaint;
private final RectF mRectF;
private final int mBitmapWidth;
private final int mBitmapHeight;
public RoundImage(Bitmap bitmap) {
mBitmap = bitmap;
mRectF = new RectF();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
final BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(shader);
mBitmapWidth = mBitmap.getWidth();
mBitmapHeight = mBitmap.getHeight();
}
#Override
public void draw(Canvas canvas) {
canvas.drawOval(mRectF, mPaint);
}
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mRectF.set(bounds);
}
#Override
public void setAlpha(int alpha) {
if (mPaint.getAlpha() != alpha) {
mPaint.setAlpha(alpha);
invalidateSelf();
}
}
#Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
#Override
public int getIntrinsicWidth() {
return mBitmapWidth;
}
#Override
public int getIntrinsicHeight() {
return mBitmapHeight;
}
public void setAntiAlias(boolean aa) {
mPaint.setAntiAlias(aa);
invalidateSelf();
}
#Override
public void setFilterBitmap(boolean filter) {
mPaint.setFilterBitmap(filter);
invalidateSelf();
}
#Override
public void setDither(boolean dither) {
mPaint.setDither(dither);
invalidateSelf();
}
public Bitmap getBitmap() {
return mBitmap;
}
}
A very good solution I've found (here) does exactly what I need, except it uses it all in the ImageView itself, instead of creating a drawable. This means that I can't set it, for example, as the background of a view.
The question
How can I achieve this?
EDIT: this is the current code, and as I wanted to add border, it also has this code for it:
public class SimpleRoundedDrawable extends BitmapDrawable {
private final Path p = new Path();
private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public SimpleRoundedDrawable(final Resources res, final Bitmap bitmap) {
super(res, bitmap);
mBorderPaint.setStyle(Paint.Style.STROKE);
}
public SimpleRoundedDrawable setBorder(float borderWidth, #ColorInt int borderColor) {
mBorderPaint.setStrokeWidth(borderWidth);
mBorderPaint.setColor(borderColor);
invalidateSelf();
return this;
}
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
p.rewind();
p.addCircle(bounds.width() / 2,
bounds.height() / 2,
Math.min(bounds.width(), bounds.height()) / 2,
Path.Direction.CW);
}
#Override
public void draw(Canvas canvas) {
canvas.clipPath(p);
super.draw(canvas);
final float width = getBounds().width(), height = getBounds().height();
canvas.drawCircle(width / 2, height / 2, Math.min(width, height) / 2, mBorderPaint);
}
}
I hope this is how things should really work.
EDIT: It seems that the solution works only from specific Android version, as it doesn't work on Android 4.2.2. Instead, it shows a squared image.
EDIT: it seems that the above solution is also much less efficient than using BitmapShader (Link here). It would be really great to know how to use it within a drawable instead of within a customized ImageView
--
Here's the current modified version of the below solutions. I hope it will be handy for some people:
public class SimpleRoundedDrawable extends Drawable {
final Paint mMaskPaint = new Paint(Paint.ANTI_ALIAS_FLAG), mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Bitmap mBitmap;
int mSide;
float mRadius;
public SimpleRoundedDrawable() {
this(null);
}
public SimpleRoundedDrawable(Bitmap bitmap) {
this(bitmap, 0, 0);
}
public SimpleRoundedDrawable(Bitmap bitmap, float width, #ColorInt int color) {
mBorderPaint.setStyle(Paint.Style.STROKE);
mBitmap = bitmap;
mSide = mBitmap == null ? 0 : Math.min(bitmap.getWidth(), bitmap.getHeight());
mBorderPaint.setStrokeWidth(width);
mBorderPaint.setColor(color);
}
public SimpleRoundedDrawable setBitmap(final Bitmap bitmap) {
mBitmap = bitmap;
mSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
invalidateSelf();
return this;
}
public SimpleRoundedDrawable setBorder(float width, #ColorInt int color) {
mBorderPaint.setStrokeWidth(width);
mBorderPaint.setColor(color);
invalidateSelf();
return this;
}
#Override
protected void onBoundsChange(Rect bounds) {
if (mBitmap == null)
return;
Matrix matrix = new Matrix();
RectF src = new RectF(0, 0, mSide, mSide);
src.offset((mBitmap.getWidth() - mSide) / 2f, (mBitmap.getHeight() - mSide) / 2f);
RectF dst = new RectF(bounds);
final float strokeWidth = mBorderPaint.getStrokeWidth();
if (strokeWidth > 0)
dst.inset(strokeWidth, strokeWidth);
matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
Shader shader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
shader.setLocalMatrix(matrix);
mMaskPaint.setShader(shader);
matrix.mapRect(src);
mRadius = src.width() / 2f;
}
#Override
public void draw(Canvas canvas) {
Rect b = getBounds();
if (mBitmap != null)
canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), mRadius, mMaskPaint);
final float strokeWidth = mBorderPaint.getStrokeWidth();
if (strokeWidth > 0)
canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), mRadius + strokeWidth / 2, mBorderPaint);
}
#Override
public void setAlpha(int alpha) {
mMaskPaint.setAlpha(alpha);
invalidateSelf();
}
#Override
public void setColorFilter(ColorFilter cf) {
mMaskPaint.setColorFilter(cf);
invalidateSelf();
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
If I'm following you correctly, your Drawable class would be like so:
public class CroppedDrawable extends BitmapDrawable {
private Path p = new Path();
public CroppedDrawable(Bitmap b) {
super(b);
}
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
p.rewind();
p.addCircle(bounds.width() / 2,
bounds.height() / 2,
Math.min(bounds.width(), bounds.height()) / 2,
Path.Direction.CW);
}
#Override
public void draw(Canvas canvas) {
canvas.clipPath(p);
super.draw(canvas);
}
}
An example usage would be:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mila);
CroppedDrawable cd = new CroppedDrawable(bitmap);
imageView.setImageDrawable(cd);
Which, with your previous sample image, would give something like this:
try this minimalist custom Drawable and modify it to meet your needs:
class D extends Drawable {
Bitmap bitmap;
Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
int side;
float radius;
public D(Bitmap wrappedBitmap) {
bitmap = wrappedBitmap;
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(16);
borderPaint.setColor(0xcc220088);
side = Math.min(bitmap.getWidth(), bitmap.getHeight());
}
#Override
protected void onBoundsChange(Rect bounds) {
Matrix matrix = new Matrix();
RectF src = new RectF(0, 0, side, side);
src.offset((bitmap.getWidth() - side) / 2f, (bitmap.getHeight() - side) / 2f);
RectF dst = new RectF(bounds);
dst.inset(borderPaint.getStrokeWidth(), borderPaint.getStrokeWidth());
matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
shader.setLocalMatrix(matrix);
maskPaint.setShader(shader);
matrix.mapRect(src);
radius = src.width() / 2f;
}
#Override
public void draw(Canvas canvas) {
Rect b = getBounds();
canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), radius, maskPaint);
canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), radius + borderPaint.getStrokeWidth() / 2, borderPaint);
}
#Override public void setAlpha(int alpha) {}
#Override public void setColorFilter(ColorFilter cf) {}
#Override public int getOpacity() {return PixelFormat.TRANSLUCENT;}
}
As it seems, using "clipPath" isn't efficient and needs to have hardware-acceleration disabled on 4.3 and below.
A better solution is to use something like on this library, using BitmapShader :
https://github.com/hdodenhof/CircleImageView
which is based on :
http://www.curious-creature.com/2012/12/11/android-recipe-1-image-with-rounded-corners/
The relevant code is:
BitmapShader shader;
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);
RectF rect = new RectF(0.0f, 0.0f, width, height);
// rect contains the bounds of the shape
// radius is the radius in pixels of the rounded corners
// paint contains the shader that will texture the shape
canvas.drawRoundRect(rect, radius, radius, paint);
I still wish to know how to do it all in a drawable, if the input is a bitmap.
A quick draft of a CircleImageDrawable based on my CircleImageView library. This does not create a new Bitmap, uses a BitmapShader to achieve the desired effect and center-crops the image.
public class CircleImageDrawable extends Drawable {
private final RectF mBounds = new RectF();
private final RectF mDrawableRect = new RectF();
private final RectF mBorderRect = new RectF();
private final Matrix mShaderMatrix = new Matrix();
private final Paint mBitmapPaint = new Paint();
private final Paint mBorderPaint = new Paint();
private int mBorderColor = Color.BLACK;
private int mBorderWidth = 0;
private Bitmap mBitmap;
private BitmapShader mBitmapShader;
private int mBitmapWidth;
private int mBitmapHeight;
private float mDrawableRadius;
private float mBorderRadius;
public CircleImageDrawable(Bitmap bitmap) {
mBitmap = bitmap;
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
}
#Override
public void draw(Canvas canvas) {
canvas.drawCircle(mBounds.width() / 2.0f, mBounds.height() / 2.0f, mDrawableRadius, mBitmapPaint);
if (mBorderWidth != 0) {
canvas.drawCircle(mBounds.width() / 2.0f, mBounds.height() / 2.0f, mBorderRadius, mBorderPaint);
}
}
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mBounds.set(bounds);
setup();
}
private void setup() {
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setShader(mBitmapShader);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderRect.set(mBounds);
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
mDrawableRect.set(mBorderRect);
mDrawableRect.inset(mBorderWidth, mBorderWidth);
mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);
updateShaderMatrix();
invalidateSelf();
}
private void updateShaderMatrix() {
float scale;
float dx = 0;
float dy = 0;
mShaderMatrix.set(null);
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
scale = mDrawableRect.height() / (float) mBitmapHeight;
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
} else {
scale = mDrawableRect.width() / (float) mBitmapWidth;
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
}
mShaderMatrix.setScale(scale, scale);
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
mBitmapShader.setLocalMatrix(mShaderMatrix);
}
#Override
public void setAlpha(int alpha) {
mBitmapPaint.setAlpha(alpha);
invalidateSelf();
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
mBitmapPaint.setColorFilter(colorFilter);
invalidateSelf();
}
#Override
public int getOpacity() {
return 0;
}
}
There is already a built-in way to accomplish this and it's 1 line of code (ThumbnailUtils.extractThumbnail())
int dimension = getSquareCropDimensionForBitmap(bitmap);
bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension);
...
//I added this method because people keep asking how
//to calculate the dimensions of the bitmap...see comments below
public int getSquareCropDimensionForBitmap(Bitmap bitmap)
{
//If the bitmap is wider than it is tall
//use the height as the square crop dimension
if (bitmap.getWidth() >= bitmap.getHeight())
{
dimension = bitmap.getHeight();
}
//If the bitmap is taller than it is wide
//use the width as the square crop dimension
else
{
dimension = bitmap.getWidth();
}
}
If you want the bitmap object to be recycled, you can pass options that make it so:
bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
Below is the link for the documentation:
ThumbnailUtils Documentation
I'm trying to mimic something from the iPhone version of my app. I have a square image and I want to show it in a circle with a white border around it. like this
Is there a way I can do this?
You can achieve this effect, or something very close to it, using a custom Drawable class, containing a Paint object with a BitmapShader that renders the image as a texture. This is the code I'm using (slightly adapted from Romain's Guy post, which uses the same technique to draw images with rounded corners).
class CircularDrawable extends Drawable
{
private float mCircleRadius;
private final RectF mBackgroundRect = new RectF();
private final Paint mBackgroundPaint;
private final BitmapShader mBitmapShader;
private final Paint mPaint;
private final int mMargin;
CircularDrawable(Bitmap bitmap, int margin, int backgroundColor)
{
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setShader(mBitmapShader);
mMargin = margin;
mBackgroundPaint = new Paint();
mBackgroundPaint.setColor(backgroundColor);
}
#Override
protected void onBoundsChange(Rect bounds)
{
super.onBoundsChange(bounds);
mBackgroundRect.set(bounds);
mCircleRadius = Math.min(bounds.width() / 2 - mMargin, bounds.height() / 2 - mMargin);
}
#Override
public void draw(Canvas canvas)
{
canvas.drawRect(mBackgroundRect, mBackgroundPaint);
canvas.drawCircle(mBackgroundRect.width() / 2, mBackgroundRect.height() / 2, mCircleRadius, mPaint);
}
#Override
public int getOpacity()
{
return PixelFormat.TRANSLUCENT;
}
#Override
public void setAlpha(int alpha)
{
mPaint.setAlpha(alpha);
mBackgroundPaint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf)
{
mPaint.setColorFilter(cf);
mBackgroundPaint.setColorFilter(cf);
}
}
Having the bitmap you want to draw, just build a CircularDrawable from it with
new CircularDrawable(bitmap, margin, Color.WHITE);
Try this.
public class CircularImageView extends ImageView {
private int borderWidth;
private int viewWidth;
private int viewHeight;
private Bitmap image;
private Paint paint;
private Paint paintBorder;
private BitmapShader shader;
public CircularImageView(final Context context) {
this(context, null);
}
public CircularImageView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.circularImageViewStyle);
}
public CircularImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// init paint
paint = new Paint();
paint.setAntiAlias(true);
paintBorder = new Paint();
paintBorder.setAntiAlias(true);
// load the styled attributes and set their properties
TypedArray attributes = context.obtainStyledAttributes(attrs,
R.styleable.CircularImageView, defStyle, 0);
if (attributes.getBoolean(R.styleable.CircularImageView_border, true)) {
setBorderWidth(attributes.getColor(
R.styleable.CircularImageView_border_width, 4));
setBorderColor(attributes.getInt(
R.styleable.CircularImageView_border_color, Color.WHITE));
}
if (attributes.getBoolean(R.styleable.CircularImageView_shadow, false))
addShadow();
}
public void setBorderWidth(int borderWidth) {
this.borderWidth = borderWidth;
this.invalidate();
}
public void setBorderColor(int borderColor) {
if (paintBorder != null)
paintBorder.setColor(borderColor);
this.invalidate();
}
public void addShadow() {
setLayerType(LAYER_TYPE_SOFTWARE, paintBorder);
paintBorder.setShadowLayer(4.0f, 0.0f, 2.0f, Color.BLACK);
}
#SuppressLint("DrawAllocation")
#Override
public void onDraw(Canvas canvas) {
// load the bitmap
BitmapDrawable bitmapDrawable = (BitmapDrawable) this.getDrawable();
if (bitmapDrawable != null)
image = bitmapDrawable.getBitmap();
// init shader
if (image != null) {
shader = new BitmapShader(Bitmap.createScaledBitmap(image,
canvas.getWidth(), canvas.getHeight(), false),
Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(shader);
int circleCenter = viewWidth / 2;
// circleCenter is the x or y of the view's center
// radius is the radius in pixels of the cirle to be drawn
// paint contains the shader that will texture the shape
canvas.drawCircle(circleCenter + borderWidth, circleCenter
+ borderWidth, circleCenter + borderWidth - 4.0f,
paintBorder);
canvas.drawCircle(circleCenter + borderWidth, circleCenter
+ borderWidth, circleCenter - 4.0f, paint);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec, widthMeasureSpec);
viewWidth = width - (borderWidth * 2);
viewHeight = height - (borderWidth * 2);
setMeasuredDimension(width, height);
}
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = viewWidth;
}
return result;
}
private int measureHeight(int measureSpecHeight, int measureSpecWidth) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpecHeight);
int specSize = MeasureSpec.getSize(measureSpecHeight);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = viewHeight;
}
return (result + 2);
}
}
I'd make a custom view and just draw what you want to the canvas- draw the border, then the white circle, then the image.It's a couple of easy canvas calls. If you need to clip the image to a circular area, just set a clipping Region before doing the image draw.
I have the following oval GradientDrawable created programmatically to display a circle:
GradientDrawable drawable = new GradientDrawable();
drawable.setColor(Color.TRANSPARENT);
drawable.setShape(GradientDrawable.OVAL);
drawable.setStroke(wheelStrokeWidth, Color.parseColor(WHEEL_STROKE_COLOR));
drawable.setSize(2*wheelRadius+wheelStrokeWidth,2*wheelRadius+wheelStrokeWidth);
ImageView iv = new ImageView(activity);
iv.setImageDrawable(drawable);
I would like to animate the drawing of this circle, to make it draw progressively. First it's just an arc of 0°, then 1°, then progressively it goes to a full 360° circle (sorry if I'm not clear, have trouble to express it in English).
Anyone has an idea on how to do that?
Thank you!
This is not a perfect solution. but this may give you some Idea.
public class MyView extends View {
private Bitmap mBitmap;
private Paint mPaint;
private RectF mOval;
private float mAngle = 135;
private Paint mTextPaint;
public MyView(Context context) {
super(context);
// use your bitmap insted of R.drawable.ic_launcher
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.image1);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mOval = new RectF();
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextSize(48);
mTextPaint.setTextAlign(Align.CENTER);
mTextPaint.setColor(0xffffaa00);
mTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Matrix matrix = new Matrix();
RectF src = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
RectF dst = new RectF(0, 0, w, h);
matrix.setRectToRect(src, dst, ScaleToFit.CENTER);
Shader shader = new BitmapShader(mBitmap, TileMode.CLAMP,
TileMode.CLAMP);
shader.setLocalMatrix(matrix);
mPaint.setShader(shader);
matrix.mapRect(mOval, src);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xff0000aa);
canvas.drawArc(mOval, -90, mAngle, true, mPaint);
canvas.drawText("click me", getWidth() / 2, getHeight() / 2,
mTextPaint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float w2 = getWidth() / 2f;
float h2 = getHeight() / 2f;
mAngle = (float) Math.toDegrees(Math.atan2(event.getY() - h2,
event.getX() - w2));
mAngle += 90 + 360;
mAngle %= 360;
if (mAngle == 0) {
mAngle = 360;
}
invalidate();
return true;
}
}
And Credit goes to some other user. He posted this solution at some where (I forgot) and I used that...
By following the idea on this post: Android custom Animation for an ArcShape, I changed my GradientDrawable to a custom View, and made this class to draw progressively an arc:
/**
* An arc with an animation to draw it progressively
*
* Usage:
*
* AnimatedArc aa = new AnimatedArc(...)
* AnimatedArc.ProgressiveDrawing animation = aa.getAnimation(...)
* aa.startAnimation(animation)
*/
public class AnimatedArc extends View {
int delta, startAngle, sweepAngle, currentSweepAngle;
Paint p = new Paint();
Rect rect = new Rect();
RectF rectF = new RectF();
public AnimatedArc(Context context, int strokeWidth, String color, int startAngle, int sweepAngle, boolean doNotAnimate) {
super(context);
p.setAntiAlias(true);
p.setColor(Color.parseColor(color));
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(strokeWidth);
delta = strokeWidth / 2;
this.startAngle = startAngle;
this.sweepAngle = sweepAngle;
this.currentSweepAngle = doNotAnimate ? sweepAngle : 0;
}
public ProgressiveDrawing getAnimation(int duration) {
return new ProgressiveDrawing(duration);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.getClipBounds(rect);
rectF.set(rect.left+delta, rect.top+delta, rect.right-delta, rect.bottom-delta);
canvas.drawArc(rectF, startAngle, currentSweepAngle, false, p);
}
public class ProgressiveDrawing extends Animation {
public ProgressiveDrawing(int duration) {
setDuration(duration);
setInterpolator(new LinearInterpolator());
}
public ProgressiveDrawing(int duration, Interpolator interpolator) {
setDuration(duration);
setInterpolator(interpolator);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
currentSweepAngle = (int) (sweepAngle * interpolatedTime);
postInvalidate();
}
}
}
I need to make cross fade effect on two bitmaps in a way that first bitmap is being replaced by the second one linearly from top to bottom. In other words first bitmap should
dissapear from top to bottom and in it's place second bitmap should appear.
There are many examples with cross fade with animations (changing alpha), or using TransitionDrawable, but i was not able to find the one i am looking for.
Anyone knows how to do that?
Thanks
i know, try this custom class:
public class MyView extends View implements View.OnClickListener {
private Bitmap mBitmap0;
private Bitmap mBitmap1;
private Matrix mMatrix;
private float mY;
private Rect mRect;
public MyView(Context context) {
super(context);
mBitmap0 = BitmapFactory.decodeResource(getResources(), R.drawable.b0);
mBitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.b1);
mMatrix = new Matrix();
mRect = new Rect();
setOnClickListener(this);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
RectF src = new RectF(0, 0, mBitmap0.getWidth(), mBitmap0.getHeight());
RectF dst = new RectF(0, 0, w, h);
mMatrix.setRectToRect(src, dst, ScaleToFit.CENTER);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xff0000aa);
canvas.concat(mMatrix);
Rect r = mRect;
r.set(0, 0, mBitmap0.getWidth(), (int) (mY * mBitmap0.getHeight()));
canvas.drawBitmap(mBitmap0, r, r, null);
r.set(0, (int) (mY * mBitmap1.getHeight()), mBitmap1.getWidth(), mBitmap1.getHeight());
canvas.drawBitmap(mBitmap1, r, r, null);
}
#Override
public void onClick(View v) {
TBAnimation anim = new TBAnimation();
startAnimation(anim);
}
class TBAnimation extends Animation {
public TBAnimation() {
mY = 0;
setDuration(2000);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mY = interpolatedTime;
invalidate();
}
}
}
or this one (has narrow fade out strip):
public class MyView extends View implements View.OnClickListener {
private static final int FADE_HEIGHT = 32;
private Bitmap mBitmap0;
private Bitmap mBitmap1;
private Matrix mMatrix;
private float mY;
private Rect mRect;
private Paint mPaint0;
private LinearGradient mShader;
private Paint mPaint1;
public MyView(Context context) {
super(context);
mBitmap0 = BitmapFactory.decodeResource(getResources(), R.drawable.b1);
mBitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.b0);
mMatrix = new Matrix();
mRect = new Rect();
mPaint0 = new Paint(Paint.ANTI_ALIAS_FLAG);
mShader = new LinearGradient(0, 0, 0, FADE_HEIGHT, 0x00ffffff, 0xffffffff, TileMode.CLAMP);
mPaint0.setShader(mShader);
mPaint1 = new Paint();
mPaint1.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
setOnClickListener(this);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
RectF src = new RectF(0, 0, mBitmap0.getWidth(), mBitmap0.getHeight());
RectF dst = new RectF(0, 0, w, h);
mMatrix.setRectToRect(src, dst, ScaleToFit.FILL);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xff0000aa);
canvas.concat(mMatrix);
Rect r = mRect;
int y = (int) (mY * mBitmap0.getHeight());
// draw new bitmap on top
r.set(0, 0, mBitmap0.getWidth(), y + FADE_HEIGHT);
canvas.drawBitmap(mBitmap0, r, r, null);
// draw old bitmap strip fading out
canvas.saveLayer(0, y, mBitmap1.getWidth(), y + FADE_HEIGHT, null, Canvas.ALL_SAVE_FLAG);
canvas.save();
canvas.translate(0, y);
r.set(0, 0, mBitmap1.getWidth(), FADE_HEIGHT);
canvas.drawRect(r, mPaint0);
canvas.restore();
r.set(0, y, mBitmap1.getWidth(), y + FADE_HEIGHT);
canvas.drawBitmap(mBitmap1, r, r, mPaint1);
canvas.restore();
// draw old bitmap on bottom
r.set(0, y + FADE_HEIGHT, mBitmap1.getWidth(), mBitmap1.getHeight());
canvas.drawBitmap(mBitmap1, r, r, null);
}
#Override
public void onClick(View v) {
Bitmap tmp = mBitmap0;
mBitmap0 = mBitmap1;
mBitmap1 = tmp;
TBAnimation anim = new TBAnimation();
startAnimation(anim);
}
class TBAnimation extends Animation {
public TBAnimation() {
mY = 0;
setDuration(3000);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mY = interpolatedTime;
invalidate();
}
}
}