I'm using the below Java class (Found on the web) as a countdown timer circle animation, The issue is that on some devices like Huawei Mate 10 Pro & Huawei G8 the animation finishes sooner than it is supposed to while on the other couple of devices that I've tested its working perfectly.
For example on Mate 10 pro, when I call the class to start the timer with '10' as the parameter (circle animation supposed to be completed in 10 seconds but it goes faster and finishes in exactly half the time-5 seconds!-) while in another device with same Android version (Android 10) it's working perfectly (Completes in 10 seconds).
So I figured the android version is not the problem here, Any idea what may be the issue ??
package com.maxwellforest.blogtimer;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.borsam.bleexample.R;
import java.util.concurrent.TimeUnit;
/**
* Countdown timer view.
*
* #author Mike Gouline
*/
public class TimerView extends View {
private static final int ARC_START_ANGLE = 270; // 12 o'clock
private static final float THICKNESS_SCALE = 0.03f;
private Bitmap mBitmap;
private Canvas mCanvas;
private RectF mCircleOuterBounds;
private RectF mCircleInnerBounds;
private Paint mCirclePaint;
private Paint mEraserPaint;
private float mCircleSweepAngle;
private ValueAnimator mTimerAnimator;
public TimerView(Context context) {
this(context, null);
}
public TimerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TimerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
int circleColor = R.color.colorPrimary;
if (attrs != null) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TimerView);
if (ta != null) {
circleColor = ta.getColor(R.styleable.TimerView_circleColor, circleColor);
ta.recycle();
}
}
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(true);
mCirclePaint.setColor(circleColor);
mEraserPaint = new Paint();
mEraserPaint.setAntiAlias(true);
mEraserPaint.setColor(Color.TRANSPARENT);
mEraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
#SuppressWarnings("SuspiciousNameCombination")
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec); // Trick to make the view square
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mBitmap.eraseColor(Color.TRANSPARENT);
mCanvas = new Canvas(mBitmap);
}
super.onSizeChanged(w, h, oldw, oldh);
updateBounds();
}
#Override
protected void onDraw(Canvas canvas) {
mCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
if (mCircleSweepAngle > 0f) {
mCanvas.drawArc(mCircleOuterBounds, ARC_START_ANGLE, mCircleSweepAngle, true, mCirclePaint);
mCanvas.drawOval(mCircleInnerBounds, mEraserPaint);
}
canvas.drawBitmap(mBitmap, 0, 0, null);
}
public void start(int secs) {
stop();
mTimerAnimator = ValueAnimator.ofFloat(0f, 1f);
mTimerAnimator.setDuration(TimeUnit.SECONDS.toMillis(secs));//(secs*1000);
mTimerAnimator.setInterpolator(new LinearInterpolator());
mTimerAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
drawProgress((float) animation.getAnimatedValue());
}
});
mTimerAnimator.start();
}
public void stop() {
if (mTimerAnimator != null && mTimerAnimator.isRunning()) {
mTimerAnimator.cancel();
mTimerAnimator = null;
drawProgress(0f);
}
}
private void drawProgress(float progress) {
mCircleSweepAngle = 360 * progress;
invalidate();
}
private void updateBounds() {
final float thickness = getWidth() * THICKNESS_SCALE;
mCircleOuterBounds = new RectF(0, 0, getWidth(), getHeight());
mCircleInnerBounds = new RectF(
mCircleOuterBounds.left + thickness,
mCircleOuterBounds.top + thickness,
mCircleOuterBounds.right - thickness,
mCircleOuterBounds.bottom - thickness);
invalidate();
}
}
There is an option in Developer options "Animator duration scale", by default this value is 1x if they are different for devices then you may experience slower or faster animations based on the value set.
Related
The intention is that the rubber/eraser interacted by user touch take two or more passes over to completely erase the image.
The current code is erasing the bitmap with only touch and I'm working on this direction with no success, any recommendations is also welcome to help
MainActivity.class
import android.os.Bundle;
public class MainActivity extends androidx.appcompat.app.AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new Rachet(MainActivity.this));
}
}
Rachet.class
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class Rachet extends androidx.appcompat.widget.AppCompatImageView {
private float mX, mY;
private Canvas canvas;
private Bitmap bitmap;
private Paint bitmapPaint = new Paint(Paint.DITHER_FLAG);
private Path rubberPath = new Path();
private Paint rubberPaint;
public Rachet(Context context) {
super(context);
init();
}
public Rachet(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Rachet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void init() {
rubberPaint = new Paint();
rubberPaint.setAntiAlias(true);
rubberPaint.setDither(true);
rubberPaint.setColor(0x0000000);
rubberPaint.setStyle(Paint.Style.STROKE);
rubberPaint.setStrokeJoin(Paint.Join.BEVEL);
rubberPaint.setStrokeCap(Paint.Cap.ROUND);
rubberPaint.setStrokeWidth(100);
rubberPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR
));
}
#Override
protected void onSizeChanged(int width, int height, int oldw, int oldh) {
super.onSizeChanged(width, height, oldw, oldh);
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.foreground), width, height, true));
bitmapDrawable.setBounds(new Rect(0, 0, width, height));
bitmapDrawable.draw(canvas);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap, 0, 0, bitmapPaint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Float x = event.getX();
Float y = event.getY();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
rubberPath.reset();
rubberPath.moveTo(x, y);
mX = x;
mY = y;
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
rubberPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
if (event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_UP) {
rubberPath.lineTo(mX, mY);
canvas.drawPath(rubberPath, rubberPaint);
rubberPath.reset();
rubberPath.moveTo(mX, mY);
}
invalidate();
return true;
}
}
This is an example of a expected result
I'm trying to achieve this custom wave animation with circle in the middle of the wave.
Below is my custom view. It runs in a different direction and the draw has a line in the middle of the wave that results in a bad UX.
I try to follow some related tutorials but I cannot get the same animation.
If there is any library o code sample to follow it could help me a lot.
Thanks.
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.guille.stressmeterapp.R;
import org.jetbrains.annotations.Nullable;
public class WaveCustomView extends View {
private int mWidth = 0;
private int mHeight = 0;
private Path path;
private Paint paint;
private int waveHeight = 300;
private int waveWidth = 600;
private int originalY = 750;
private Region region;
private int dx = 0;
private Bitmap mBitmap;
private int animationDuration = 3000;
public WaveCustomView(Context context) {
super(context, null);
initUi();
}
public WaveCustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs, 0);
initUi();
}
public WaveCustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initUi();
}
private void initUi() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.parseColor("#000000"));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(15);
path = new Path();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.circle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
int desired = (int) (getPaddingLeft() + getPaddingRight());
width = desired;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
int desired = (int) (getPaddingTop() + getPaddingBottom());
height = desired;
}
mWidth = width;
mHeight = height;
waveWidth = mWidth / 2;
setMeasuredDimension(width, height);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, paint);
setDrawData();
Rect bounds = region.getBounds();
if (bounds.top < originalY) {
canvas.drawBitmap(mBitmap, bounds.right - (mBitmap.getWidth() >> 1), bounds.top - (mBitmap.getHeight() >> 1), paint);
} else {
canvas.drawBitmap(mBitmap, bounds.right - (mBitmap.getWidth() >> 1), bounds.bottom - (mBitmap.getHeight() >> 1), paint);
}
}
private void setDrawData() {
path.reset();
int halfWaveWidth = waveWidth / 2;
path.moveTo(-waveWidth + dx, originalY);
for (int i = -waveWidth; i < mWidth + waveWidth; i = i + waveWidth) {
path.rQuadTo(halfWaveWidth >> 1, -waveHeight, halfWaveWidth, 0);
path.rQuadTo(halfWaveWidth >> 1, waveHeight, halfWaveWidth, 0);
}
region = new Region();
Region clip = new Region((int) (mWidth / 2 - 0.1), 0, mWidth / 2, mHeight * 2);
region.setPath(path, clip);
path.close();
}
public void startAnimate() {
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float factor = (float) valueAnimator.getAnimatedValue();
dx = (int) ((waveWidth) * factor);
invalidate();
}
});
animator.setDuration(animationDuration);
animator.start();
}
Your code looks OK. Just remove this line from setDrawData method.
path.close();
This line closes path. It means it connect path begginnig with path end. That's why you see line in the middle of the wave.
Here is result without middle line:
If you want to change the direction of animation just change sign of dx variable. Change this:
dx = (int) ((waveWidth) * factor);
To this:
dx = - (int) (waveWidth * factor);
Or instead of this:
path.moveTo(-waveWidth + dx, originalY);
Do this:
path.moveTo(-waveWidth - dx, originalY);
Final result:
This is my first time using Canvas in Android.
I am creating an app that would display circles at certain positions on the screen one at a time (positions are selected randomly). New circle should be drawn after the previous one was selected/touched, and the previous one should disappear.
I have some ideas about it: to keep an arraylist of Point objects(each object contains x,y coordinate of the centre of the circle) and randomly select one each time the circle is drawn on the screen. So first I am populating an array of points. I also know how to randomly select the element from arraylist.
My biggest confustion is how to connect onDraw and onTouchEvent methods with each other? I know I should check if the circle was selected and only then draw a new circle at the randomly selected position, but I m not sure how to make a call for onDraw() method from the onTouchEvent...
Could you please help with this issue?
My code is below:
package com.example.researcher.heatmap;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* TODO: document your custom view class.
*/
public class MyView extends View {
Paint paint;
ArrayList<Point> points = new ArrayList<>();
public MyView(Context context) {
super(context);
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
// Load attributes
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.STROKE);
populateArrayList();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.STROKE);
canvas.drawColor(Color.WHITE);
int i=1; // should be random, will randomize later
for(Point p: points) {
p.x = points.get(i).x;
p.y = points.get(i).y;
canvas.drawCircle(p.x, p.y, 50, paint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int i=1;
for(Point p: points) {
Canvas canvas = new Canvas();
p.x = points.get(i).x;
p.y = points.get(i).y;
canvas.drawCircle(p.x, p.y, 50, paint);
}
postInvalidate();
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
break;
}
}
postInvalidate();
return true;
}
public void populateArrayList(){
points.clear();
points.add(new Point(120, 120));
points.add(new Point(150, 320));
points.add(new Point(280, 200));
}
}
Thank you Chris for your help! I really appreciate it.
Here is my solution if someone will need it for reference
package com.example.researcher.heatmap;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* TODO: document your custom view class.
*/
public class MyView extends View {
Paint paint;
ArrayList<Point> points = new ArrayList<>();
private int pointsPos = 0; //Which point we will be drawing
public float x;
public float y;
public int radius = 150;
public MyView(Context context) {
super(context);
x = this.getX();
y = this.getY();
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
x = this.getX();
y = this.getY();
init();
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
x = this.getX();
y = this.getY();
init();
}
private void init() {
// Load attributes
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.STROKE);
populateArrayList();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.STROKE);
canvas.drawColor(Color.WHITE);
canvas.drawCircle(points.get(pointsPos).x, points.get(pointsPos).y, radius, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
//Check if the point press is within the circle
if(contains(event, points.get(pointsPos))){
Random r = new Random(System.nanoTime());
pointsPos = r.nextInt(points.size());; //between 0 and points.length
postInvalidate();
}
case MotionEvent.ACTION_CANCEL: {
break;
}
}
postInvalidate();
return true;
}
private boolean contains(MotionEvent event, Point point) {
float xTouch = event.getX();
float yTouch = event.getY();
if ((xTouch - point.x) * (xTouch - point.x) + (yTouch - point.y) * (yTouch - point.y) <= radius * radius) {
return true;
}
else {
return false;
}
}
public void populateArrayList(){
points.clear();
points.add(new Point(220, 1020));
points.add(new Point(550, 320));
points.add(new Point(780, 500));
}
}
Class var
private int state = 0 //0 normal, 1 new circle
private int pointPos = 0; //Which point we will be drawing
onDraw was overwriting the x/y of all your other points
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.STROKE);
canvas.drawColor(Color.WHITE);
if(state == 1){
pointPos = random(); //between 0 and points.length
state = 0;
}
canvas.drawCircle(points.get(pointsPos).x, points.get(pointsPos).y, 50, paint);
}
onTouchEvent: Drawing should only be done on the ondraw, use flags/states to keep track of what you should be doing on the next draw call
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//Though these days this is usually done on the up event
//Check if the point press is within the circle
if(contains(event, points.get(pointPos))){
state = 1;
postInvalidate();
}
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
break;
}
}
postInvalidate();
return true;
}
I'm new to Android development, and trying to render a button to a Canvas object in an onDraw method, basically text over a backfield. It's a good way to get my feet wet with some of the rendering commands.
I am able to fill a solid rectangle and then draw centered text over it, but when I try to fill a gradient rectangle, and then draw text over it, the text doesn't draw.
Code is below, cobbled together from various examples. Basically:
DO_PAINT=0, DO_GRADIENT=0 -> text renders
DO_PAINT=1, DO_GRADIENT=0 -> solid rectangle with text on top
DO_PAINT=0, DO_GRADIENT=1 -> gradient rectangle (no text) !!!
So something about my gradient drawing interferes with my text rendering. I'm guessing that I'm leaving something in a bad state in the Paint object, but I'm not sure what property that would be...
Any insight or thoughts are greatly appreciated...
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.view.View;
import android.graphics.Paint;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Canvas;
import android.util.Log;
import android.view.MotionEvent;
import android.app.Activity;
public class cMyView extends View
{
public cMyView(Context context, Activity owner_activity)
{
super(context);
}
final Paint m_paint = new Paint();
public String m_Text = "Button";
private final Rect textBounds = new Rect();
public Typeface m_TypeFace = Typeface.create("Arial",Typeface.NORMAL);
public int m_TextColor = Color.argb(255,0,0,0);
public int m_TextSize = 32;
#Override
protected void onDraw(Canvas canvas) {
Rect m_Bounds = new Rect(100,100,500,200);
boolean DO_PAINT = false;
boolean DO_GRADIENT = true;
if ( DO_PAINT) {
m_paint.setStyle(Paint.Style.FILL);
m_paint.setColor(Color.GREEN);
canvas.drawRect(m_Bounds, m_paint);
}
if (DO_GRADIENT) {
m_paint.setShader(new LinearGradient(0, m_Bounds.top, 0, m_Bounds.bottom, Color.BLACK, Color.WHITE, Shader.TileMode.MIRROR));
canvas.drawRect(m_Bounds.left, m_Bounds.top, m_Bounds.right, m_Bounds.bottom, m_paint);
}
m_paint.setColor(m_TextColor);
m_paint.setTextSize(m_TextSize);
m_paint.setTypeface(m_TypeFace);
m_paint.getTextBounds(m_Text, 0, m_Text.length(), textBounds);
double x = m_Bounds.left + m_Bounds.width()/2 - textBounds.exactCenterX();
double y = m_Bounds.top + m_Bounds.height()/2 - textBounds.exactCenterY();
canvas.drawText(m_Text, (float) x, (float) y, m_paint);
}
}
Just add an another Paint for text, this is worked for me, and I found the reason, if you comment 2-nd row in DO_GRADIENT case (in your code), then will see that text is gradient, it mean that it draw, but have same gradient as background have, and becomes invisible.
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
final Paint m_paint = new Paint();
public String m_Text = "Button";
private final Rect textBounds = new Rect();
public Typeface m_TypeFace = Typeface.create("Arial",Typeface.NORMAL);
public int m_TextColor = Color.argb(255, 0, 0, 0);
public int m_TextSize = 32;
private final Paint textPaint = new Paint();
#Override
protected void onDraw(Canvas canvas) {
Rect m_Bounds = new Rect(100,100,500,200);
boolean DO_PAINT = true;
boolean DO_GRADIENT = true;
if ( DO_PAINT) {
m_paint.setStyle(Paint.Style.FILL);
m_paint.setColor(Color.GREEN);
canvas.drawRect(m_Bounds, m_paint);
}
if (DO_GRADIENT) {
m_paint.setShader(new LinearGradient(0, m_Bounds.top, 0, m_Bounds.bottom, Color.BLACK, Color.WHITE, Shader.TileMode.MIRROR));
canvas.drawRect(m_Bounds.left, m_Bounds.top, m_Bounds.right, m_Bounds.bottom, m_paint);
}
m_paint.setColor(m_TextColor);
m_paint.setTextSize(m_TextSize);
m_paint.setTypeface(m_TypeFace);
m_paint.getTextBounds(m_Text, 0, m_Text.length(), textBounds);
double x = m_Bounds.left + m_Bounds.width()/2 - textBounds.exactCenterX();
double y = m_Bounds.top + m_Bounds.height()/2 - textBounds.exactCenterY();
textPaint.setColor(m_TextColor);
textPaint.setTextSize(m_TextSize);
textPaint.setTypeface(m_TypeFace);
canvas.drawText(m_Text, (float) x, (float) y, textPaint);
}
I'm trying to show an animated gif. By the way I'm doing it with the class Movie. But the Android Developer page dont grant info about the methods.
How can I make the gif to be resized to fit the layout?
Thanks in advance
I've been trying to do the same thing (to display animated GIF) using this method.
It works only if you specify uses-sdk android:minSdkVersion="3"
For scaling ...
package com.example.GIFShow;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
class MYGIFView extends View {
private static final boolean twigDebug = true;
private Movie mMovie;
private long mMovieStart;
MYGIFView(Context aContext, Movie aMovie) {
super(aContext);
if (aMovie == null)
return;
mMovie = aMovie;
mMovieStart = android.os.SystemClock.uptimeMillis();
}
protected void onDraw(Canvas aCanvas) {
super.onDraw(aCanvas);
if (mMovie == null || mMovie.duration() == 0)
return;
int relTime = (int)((android.os.SystemClock.uptimeMillis() - mMovieStart)
% mMovie.duration());
mMovie.setTime(relTime);
Bitmap movieBitmap = Bitmap.createBitmap(mMovie.width(), mMovie.height(),
Bitmap.Config.ARGB_8888);
Canvas movieCanvas = new Canvas(movieBitmap);
mMovie.draw(movieCanvas, 0, 0);
Rect src = new Rect(0, 0, mMovie.width(), mMovie.height());
Rect dst = new Rect(0, 0, this.getWidth(), this.getHeight());
aCanvas.drawBitmap(movieBitmap, src, dst, null);
this.invalidate();
}
}
Now you can get and pass a Movie object to the class in one of two ways ...
Get input GIF Movie object from project drawable folder
Movie movie = Movie.decodeStream
(context.getResources().openRawResource(R.drawable.somefile));
Or get input GIF Movie object from external storage
(/mnt/sdcard ... /mnt/extSdCard ... etc)
Movie movie = null;
try {
FileInputStream stream =
new FileInputStream(Environment.getExternalStorageDirectory() +
"/somefolder/somefile.gif");
try {
byte[] byteStream = streamToBytes(stream);
movie = Movie.decodeByteArray(byteStream, 0, byteStream.length);
}
finally {
stream.close();
}
}
catch (IOException e) { }
Now set the moving GIF image / Movie object into your activity view:
View view = new MYGIFView(this, movie);
setContentView(view);
If you get the GIF image / Movie object from external storage (second example) you'll need the supporting routine:
private byte[] streamToBytes(InputStream is) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
byte[] buffer = new byte[1024];
int len;
try {
while ((len = is.read(buffer)) >= 0)
os.write(buffer, 0, len);
}
catch (java.io.IOException e) { }
return os.toByteArray();
}
You answer is good. It helped me alot.I have changed one thing for performance .Moved the bitmap creation to outside of ondraw
import java.io.InputStream;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Movie;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.View;
public class GifView extends View {
private Movie mMovie;
InputStream mStream;
long mMoviestart;
private Context context;
public GifView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init();
}
public GifView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public GifView(Context context) {
super(context);
this.context = context;
init();
}
Bitmap movieBitmap = null;
private void init() {
InputStream object = this.getResources().openRawResource(
R.raw.postloadinganimation);
mMovie = Movie.decodeStream(object);// context.getResources().getAssets().open("PostLoadingAnimation.gif"));
movieBitmap = Bitmap.createBitmap(mMovie.width(),
mMovie.height(), Bitmap.Config.ARGB_8888);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
super.onDraw(canvas);
final long now = SystemClock.uptimeMillis();
if (mMoviestart == 0) {
mMoviestart = now;
}
final int relTime = (int) ((now - mMoviestart) % mMovie.duration());
mMovie.setTime(relTime);
// mMovie.draw(canvas, 0, 0);
Canvas movieCanvas = new Canvas(movieBitmap);
mMovie.draw(movieCanvas, 0, 0);
Rect src = new Rect(0, 0, mMovie.width(), mMovie.height());
Rect dst = new Rect(0, 0, this.getWidth(), this.getHeight());
canvas.drawBitmap(movieBitmap, src, dst, null);
this.invalidate();
}
}
I know, this is very old post.
But you can try this ...
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMovie != null) {
int movieWidth = mMovie.width();
int movieHeight = mMovie.height();
/** Calculate horizontal scaling*/
float scaleH = 1f;
int measureModeWidth = MeasureSpec.getMode(widthMeasureSpec);
if (measureModeWidth != MeasureSpec.UNSPECIFIED) {
int maximumWidth = MeasureSpec.getSize(widthMeasureSpec);
if (movieWidth > maximumWidth) {
scaleH = (float) movieWidth / (float) maximumWidth;
}else{
scaleH = (float) maximumWidth / (float) movieWidth;
}
}
/** calculate vertical scaling*/
float scaleW = 1f;
int measureModeHeight = MeasureSpec.getMode(heightMeasureSpec);
if (measureModeHeight != MeasureSpec.UNSPECIFIED) {
int maximumHeight = MeasureSpec.getSize(heightMeasureSpec);
if (movieHeight > maximumHeight) {
scaleW = (float) movieHeight / (float) maximumHeight;
}else{
scaleW = (float) maximumHeight / (float) movieHeight;
}
}
/** calculate overall scale*/
mScale = Math.max(scaleH, scaleW);
mMeasuredMovieWidth = (int) (movieWidth * mScale);
mMeasuredMovieHeight = (int) (movieHeight * mScale);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
} else {
/*No movie set, just set minimum available size.*/
setMeasuredDimension(getSuggestedMinimumWidth(), getSuggestedMinimumHeight());
}
}
private void drawMovieFrame(Canvas canvas) {
mMovie.setTime(mCurrentAnimationTime);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(mScale, mScale);
mLeft = (getWidth() - mMeasuredMovieWidth) / 2f;
mTop = (getHeight() - mMeasuredMovieHeight) / 2f;
mMovie.draw(canvas, mLeft / mScale, mTop / mScale);
canvas.restore();
}