Every time onDraw() is called I am drawing a series of points on the canvas. How do I animate one point so that it changes colour or fades in and out? So essentially goes from orange->red and back or opaque->transparent and back?
I am doing the following:
public void onDraw(Canvas canvas) {
drawDots();
}
private void drawDots() {
canvas.drawCircle(xcoord, ycoord, 20, getPaintObj(param));
}
private Paint getPaintObj(int param) {
if (param % 2 == 0) {
ObjectAnimator colorFade = ObjectAnimator.ofObject(paintObj, "color", new ArgbEvaluator(), 0xff00ff00, 0xffff0000, 0xff0000ff);
colorFade.setDuration(2000);
colorFade.setInterpolator(new LinearInterpolator());
colorFade.setRepeatCount(ValueAnimator.INFINITE);
colorFade.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
colorFade.start();
return paintObj;
} else {
return otherPaintObj;
}
}
The problem is that it doesn't animate. It sticks to the first colour (0xff00ff00). What am I doing wrong?
ObjectAnimator doesn't work very well with the canvas as I was trying to animate a circle drawn on the canvas itself. Works better for actual objects (TextView, ImageView, etc.) rather than points drawn on canvases.
The solution I used is redrawing the canvas every once in a while and modifying the radius of the circle drawn to simulate an animation.
Related
Is there a simple way to just set the transparency of the content (children) of a ViewGroup?
Background: I got a custom FrameLayout with a bunch of different views in it. Now I want to fade out only the content/children:
The backgroundColor of the layout should always be visible - so I cannot just setAlpha() on the whole ViewGroup
I do not want to loop through all the children and call child.setAlpha() on every one of them manually
Current Approach
It's working but I'm not completely satisfied with it - I think there is a simpler solution.
Override dispatchDraw() and just draw a rectangle above all content:
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// Draw the alpha overlay above the children.
if (mGridAlpha < 1.0f) {
mOverlayPaint.setColor(adjustAlpha(mOverlayPaint.getColor(), 1 - mGridAlpha));
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mOverlayPaint);
}
}
public static int adjustAlpha(int color, float factor) {
int alpha = Math.round(255 * factor);
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
return Color.argb(alpha, red, green, blue);
}
Fading in/out is done manually with a ValueAnimator:
public void fadeContent(final boolean in) {
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
mGridAlpha = in ? fraction : 1 - fraction;
// Key here is to call invalidate() which will cause a dispatchDraw() call
invalidate();
}
});
animator.start();
}
currently I am trying to make an animation where some fish move around. I have successfully add one fish and made it animate using canvas and Bitmap. But currently I am trying to add a background that I made in Photoshop and whenever I add it in as a bitmap and draw it to the canvas no background shows up and the fish starts to lag across the screen. I was wondering if I needed to make a new View class and draw on a different canvas or if I could use the same one? Thank you for the help!
Here is the code in case you guys are interested:
public class Fish extends View {
Bitmap bitmap;
float x, y;
public Fish(Context context) {
super(context);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.fish1);
x = 0;
y = 0;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap, x, y, null);
if (x < canvas.getWidth())
{
x += 7;
}else{
x = 0;
}
invalidate();
}
}
You can draw as many bitmaps as you like. Each will overlay the prior. Thus, draw your background first, then draw your other images. Be sure that in your main images, you use transparent pixels where you want the background to show through.
In your code, don't call Invalidate() - that's what causes Android to call onDraw() and should only be called from somewhere else when some data has changed and needs to be redrawn.
You can do something like this, where theView is the view containing your animation:
In your activity, put this code in onCreate()
myAnimation();
Then
private void myAnimation()
{
int millis = 50; // milliseconds between displaying frames
theView.postDelayed (new Runnable ()
{
#Override public void run()
{
theView.invalidate();
myAnimation(); // you can add a conditional here to stop the animation
}
}, millis);
}
I have a design question so I don't start off on the wrong way. I am planning to have a sprite where it animates moving and I want to move this sprite around (changing position once every second).
I noticed that some tutorial talk about having a game loop,, onDraw on Canvas, bitmap ..etc
However, I am thinking of using drawableAnimation where I specify the set of images to load in xml and call start on it. Then I can just draw at the position required every second ( no loop, it is more like listener that gets called every sec from different process).
Do you forsee an issue? Any problem with above method
Thank you
Drawable Animation requires a fixed frame animation. Think of it like a flip book. The drawable would have to be the entire size of your canvas and your sprite would have to be prerendered on top of some transparent background to achieve your affect. It also would require a large amount of texture memory depending on the size and number of frames.
The "game loop" as you stated is sort of what Android already provides in a way. Inside of the standard canvas setup you have your "main Thread". So with an entity like a Handler you could tick frames if you like.
i.e.
Handler handler;
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
handler = new Handler();
handler.post(new Runnable() {
public void run() {
boolean isLastFrame = drawFrame();
if (!isLastFrame) {
handler.postDelayed(this, ONE_SECOND_MILLIS);
}
}
});
This is not the greatest really either.
You could also encapsulate your sprite inside a View or own custom Drawable and inside your onDraw() method, you would update your canvas.
public void onDraw(Canvas canvas) {
// clear canvas
mPaint.setColor(Color.WHITE);
canvas.drawPaint(mPaint);
// Draw current
mPaint.setColor(Color.BLACK);
canvas.drawRect(rect, mPaint);
}
public void moveSprite() {
rect.offset(3, 3);
invalidate();
}
You could also, if using a View, use an ObjectAnimator to translate the View from position (x1, y1) to (x2, y2).
public void moveTo() {
if (!moveNeeded) {
return;
}
View view = getMyView();
view.animate().x(1).y(1).setListener(new Animator.AnimatorListener() {
public void onAnimationCancel(Animator a) { }
public void onAnimationStart(Animator a) {
}
public void onAnimationEnd(Animator a) {
moveTo();
}
public void onAnimationRepeat(Animator a) {
}
});
}
I have an ActionBar icon (the main one on the left, not an action item) that I would like to animate.
I am setting my ActionBar's icon in my Activity like this:
getSupportActionBar().setIcon(icon)
where icon is a Drawable produced by a library that converts a custom XML view into a bitmap. This XML view is a RelativeLayout with a background image and a TextView on top.
Today, when I have to update the TextView I simply re-generate the icon and call setIcon again. Instead, I would like to get a hold of my TextView and apply some animation effect on it, like fade-out and then fade-in after updating it (maybe never having to call setIcon, just re-use the same one).
Not sure how to go about this. Can someone recommend an approach?
EDIT: trying this approach:
In MyActivity:
Drawable myDrawable = new MyDrawable();
supportActionBar.setIcon(myDrawable);
and:
public class MyDrawable extends Drawable {
private Paint paint;
private RectF rect;
public MyDrawable() {
this.paint = new Paint();
this.rect = new RectF();
}
#Override
public void draw(Canvas canvas) {
paint.setARGB(255, 0, 255, 0);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.FILL);
rect.right = 20f;
rect.bottom = 20f;
canvas.drawRoundRect(rect, 0.5f, 0.5f, paint);
}
#Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
}
Nothing shows up. I verified that onDraw gets called. Something suspicious to me is that canvas has both height & width set to 1.
A proper approach for it would be to forget the XML layout and create a custom Drawable.
An instance of this custom drawable will be set to the icon on the ActionBar and call invalidateSelf() whenever necessary to redraw (due to animation, for example).
The drawable can hold reference to other drawables (e.g. BitmapDrawable to have something from the /res/ folder or a Color or Gradient drawable for a background shade) and call (for example) bgDraw.draw(canvas) during the onDraw callback.
It can also draw stuff directly on the canvas that is given to it during onDraw callback. With the canvas you can draw circle, lines, areas, path and text directly on it.
edit:
very simple animation example (didn't check the code, likely typos):
private long animationTime;
public void doAnimation(){
animationTime = System.currentTimeMilis() + 3000; // 3 seconds
invalidateSelf();
}
public void onDraw(Canvas canvas){
// do your drawing.
// You can use difference between
// currentTimeMilis and animationTime for status/position
...
// at the end
if(animationTime > System.currentTimeMilis())
invalidateSelf();
}
I'm trying to Use a BitmapShader to draw on a Canvas. I have 2 images of the same dimensions (one for the alfa channel and one for RGB), that I want to draw on a canvas that was created with the same size. The problem is that it seem as though canvas.drawBitmap users absolute coordinates for the paint. is there a way to draw with relative coordinates.
public class MaskedImageDrawable extends Drawable {
private final Bitmap mMask;
private final Paint mPaint = new Paint();
public MaskedImageDrawable(Bitmap mask, Bitmap image) {
mMask = mask;
Shader targetShader =
new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(targetShader);
}
#Override
public void draw(Canvas canvas) {
canvas.drawBitmap(mMask, 0.0f, 0.0f, mPaint);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
#Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
}
I can't seem to be able to get the drawable's absolute coordinates on the screen. I know I can use getLocationOnScreen/InWindow from the view that holds the drawable but that doesn't take animations into account.
I tried tried applying relative coordinates by applying a translation on the shader in onBoundsChange but the coordinates here seems to be relative as well.
Matrix mTranslationMatrix = new Matrix()
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mTranslationMatrix.setTranslate(bounds.left, bounds.top);
mPaint.getShader().setLocalMatrix(mTranslationMatrix);
}
As a reference - it seems drawRect with the same paint will use relative coordinates. so replacing the draw method with the following will render the RGB image on the drawable correctly (but not the mask)
#Override
public void draw(Canvas canvas) {
// getBounds returns the relative coordinates as well
canvas.drawRect(getBounds, mPaint);
}
It seems that this is a HW acceleration issue. disabling HW acceleration on the view moves the shader back to relative coordinates.
if (android.os.Build.VERSION.SDK_INT >= 11) {
myView.disableHwAcceleration();
}