Related
I have a problema animating a rectangle in Zxing, I am building a qr reader with this library, and my client asked me for an animated laser. No problem for this, laser animation works fine.
But he asked me a texture over laser :/ and I can't animate this.
Sincerely, code for laser is a copy past.
#Override
public void drawLaser(Canvas canvas) {
// Draw a red "laser scanner" line through the middle to show decoding is active
mLaserPaint.setAlpha(155);
int middle = mFramingRect.height() / 2 + mFramingRect.top;
middle = middle + cntr;
if ((cntr < 300) && (!goingup)) {
canvas.drawRect(mFramingRect.left + 2,
middle - 5,
mFramingRect.right - 1,
middle + 10,
mLaserPaint);
cntr = cntr + 4;
}
if ((cntr >= 300) && (!goingup)) goingup = true;
if ((cntr > -300) && (goingup)) {
canvas.drawRect(mFramingRect.left + 4,
middle - 5,
mFramingRect.right - 1,
middle + 10,
mLaserPaint);
cntr = cntr - 4;
}
if ((cntr <= -300) && (goingup)) goingup = false;
postInvalidateDelayed(ANIMATION_DELAY,
mFramingRect.left - POINT_SIZE,
mFramingRect.top - POINT_SIZE,
mFramingRect.right + POINT_SIZE,
mFramingRect.bottom + POINT_SIZE);
}
And my code for trying animate texture over laser
public void drawTexture (Canvas canvas) {
int a = 200;
int b = 220;
int c = 400;
int d = 440;
Paint paint = new Paint();
paint.setColor(Color.RED);
c = c + 100;
d = d + 100;
canvas.drawRect(a,b,c,d,paint);
invalidate();
}
both methods are called in method onDraw
public void onDraw(Canvas canvas) {
if (this.getFramingRect() != null) {
...
this.drawLaser(canvas);
this.drawTexture(canvas);
}
}
I don't know how to animate my rectangle :(, thanks for your help.
Here is my full code
https://gist.github.com/memoadian/2266fbfe6bdf5a3345f2776bdbe7bf2c
UPDATE
Ok, the animation was like this.
public void drawTexture (Canvas canvas) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.codi_texture);
BitmapShader fillBMPshader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
Paint paint = new Paint();
paint.setShader(fillBMPshader);
int middle = mFramingRect.height() / 2 + mFramingRect.top;
middle = middle + cntr;
if ((cntr < 300) && (!goingup)) {
canvas.drawRect(mFramingRect.left,
mFramingRect.top,
mFramingRect.right,
middle,
paint);
cntr = cntr + 4;
}
if ((cntr >= 300) && (!goingup)) goingup = true;
if ((cntr > -300) && (goingup)) {
canvas.drawRect(mFramingRect.left,
mFramingRect.top,
mFramingRect.right,
middle,
paint);
cntr = cntr - 4;
}
if ((cntr <= -300) && (goingup)) goingup = false;
}
And the result is
I'm use lottie for animation and disable laser and transparent rectangle, maybe this could be your next idea
mScannerView.setLaserColor(mContext.getResources().getColor(R.color.btn_color));
mScannerView.setLaserEnabled(false);
mScannerView.setBorderColor(mContext.getResources().getColor(android.R.color.transparent));
mScannerView.setMaskColor(mContext.getResources().getColor(android.R.color.transparent));
List<BarcodeFormat> enableFormat = new ArrayList<>();
enableFormat.add(BarcodeFormat.QR_CODE);
mScannerView.setFormats(enableFormat);
mScannerView.setAutoFocus(true);
try {
int padding = paddingInDp(100);
lottieView.setPadding(-padding, -padding, -padding, -padding);
lottieView.setAnimation("barcode_scan.json");
lottieView.setSpeed(0.5f);
lottieView.playAnimation();
lottieView.loop(true);
} catch (Exception e){
e.printStackTrace();
}
I've managed to draw a staircase and also a smiley face (called Avatar in the code) using paths in my custom view. Id like to move the happy face up the stairs using my nextStep function. nextStep iterates the current step and then calls a function called moveToStep(int step) which offsets the Avatar to the location of the specified step. Lastly, nextStep calls this.invalidate so that onDraw gets called again in hopes that the Avatar is redrawn at the new offset. The problem is that after nextStep is called, the Avatar disappears even though the staircase still remains. I know its not offscreen because I checked the coordinates the smiley is offset to.
Custom View Code
public class StaircaseView extends View {
// setup initial color
private final int paint_color = Color.BLACK;
private int curr_step = 1;
int STEPS = 5;
private float avatar_radius;
// defines paint and canvas
private Paint DrawPaint;
private int view_width, view_height, view_size;
private Path StaircasePath, Avatar;
private float scale, side_length;
private char constrainer;
public StaircaseView(Context context, AttributeSet attrs) {
super(context, attrs);
setupPaint();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("StaircaseView", "onDraw called");
canvas.drawPath(StaircasePath, DrawPaint);
canvas.drawPath(Avatar, DrawPaint);
}
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld){
super.onSizeChanged(xNew, yNew, xOld, yOld);
view_height = yNew;
view_width = xNew;
view_size = Math.min(view_height, view_width);
if (view_width == view_size) {
constrainer = 'w';
} else {
constrainer = 'h';
}
scale = (float)view_size/100;
Log.i("StaircaseView", "onSizeChanged --> view width: " + String.valueOf(xNew) + ", view height: " + String.valueOf(yNew) + ", scale: " + String.valueOf(scale) + ", view size: " + String.valueOf(view_size));
float padding = 5 * scale;
StaircasePath = createStaircase(padding);
avatar_radius = 7*scale;
Avatar = createAvatar(0, 0, avatar_radius);
StaircasePath.offset(0, view_height);
moveToStep(curr_step);
}
public void nextStep() {
if (curr_step < STEPS) {
curr_step++;
} else {
curr_step = 1;
}
moveToStep(curr_step);
this.invalidate();
}
private void moveToStep(int step) {
float x = step * side_length - avatar_radius;
float y = view_height - (step - 1) * side_length - avatar_radius;
Log.i("StaircaseView", String.valueOf(x) + ", " + String.valueOf(y));
Avatar.offset(x, y);
}
// Setup paint with color and stroke styles
private void setupPaint() {
DrawPaint = new Paint();
DrawPaint.setColor(paint_color);
DrawPaint.setAntiAlias(true);
DrawPaint.setStrokeWidth(5);
DrawPaint.setStyle(Paint.Style.STROKE);
DrawPaint.setStrokeJoin(Paint.Join.ROUND);
DrawPaint.setStrokeCap(Paint.Cap.ROUND);
}
private Path createStaircase(float padding) {
if (constrainer == 'w') {
side_length = (view_size-padding)/(STEPS+1);
} else {
side_length = (view_size-padding)/STEPS;
}
Path path = new Path();
float curr_x = 0;
float curr_y = 0;
path.moveTo(curr_x, curr_y);
curr_x += side_length;
path.lineTo(curr_x, curr_y);
for(int i=0; i<STEPS; i++) {
curr_y -= side_length;
path.lineTo(curr_x, curr_y);
curr_x += side_length;
path.lineTo(curr_x, curr_y);
}
path.lineTo(curr_x, 0);
path.lineTo(0, 0);
return path;
}
private Path createShape(ArrayList<PointF> points) {
Path path = new Path();
path.moveTo(points.get(0).x, points.get(0).y);
for(int i=1; i< points.size(); i++) {
path.lineTo(points.get(i).x, points.get(i).y);
}
return path;
}
private Path createAvatar(int x, int y, float radius){
Path avatar = new Path();
float width = radius*2;
avatar.addCircle(x, y, radius, Path.Direction.CW);
avatar.addCircle(x - (radius /2), y - (radius / 5), radius/5, Path.Direction.CW);
avatar.addCircle(x + (radius / 2), y - (radius / 5), radius / 5, Path.Direction.CW);
avatar.addRect((float) x - (radius / 5), (float) y - (radius / 5), (float) x + (radius / 5), (float) y - (radius/5), Path.Direction.CCW);
return avatar;
}
}
After looking at this question decided to figure out another way to do this. So what I did was I changed my moveToStep method to return the coordinates to move the Avatar to instead of offsetting him. I then passed these coordinates to the createAvatar method as start coordinates. It worked after that.
New Code
/**
* Created by yako on 11/5/15.
*/
public class StaircaseView extends View {
// setup initial color
private final int paint_color = Color.BLACK;
private int curr_step = 1;
int STEPS = 5;
private float avatar_radius;
// defines paint and canvas
private Paint DrawPaint;
private int view_width, view_height, view_size;
private Path StaircasePath, Avatar;
private float scale, side_length;
private char constrainer;
public StaircaseView(Context context, AttributeSet attrs) {
super(context, attrs);
setupPaint();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("StaircaseView", "onDraw called");
canvas.drawPath(StaircasePath, DrawPaint);
canvas.drawPath(Avatar, DrawPaint);
}
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld){
super.onSizeChanged(xNew, yNew, xOld, yOld);
view_height = yNew;
view_width = xNew;
view_size = Math.min(view_height, view_width);
if (view_width == view_size) {
constrainer = 'w';
} else {
constrainer = 'h';
}
scale = (float)view_size/100;
Log.i("StaircaseView", "onSizeChanged --> view width: " + String.valueOf(xNew) + ", view height: " + String.valueOf(yNew) + ", scale: " + String.valueOf(scale) + ", view size: " + String.valueOf(view_size));
float padding = 5 * scale;
// Log.i("StaircaseView", String.valueOf(view_height/2 + padding/2));
StaircasePath = createStaircase(padding);
// Center the staircase in the view
// if (constrainer == 'w') {
// StaircasePath.offset(padding/2, view_height - (view_height - view_width - padding*2));
// } else {
// StaircasePath.offset(view_height/2 - padding/2, view_height-padding/2);
// }
avatar_radius = 7*scale;
Avatar = createAvatar(moveToStep(curr_step), avatar_radius);
StaircasePath.offset(0, view_height);
}
public void nextStep() {
if (curr_step <= STEPS) {
curr_step++;
} else {
curr_step = 1;
}
Avatar = createAvatar(moveToStep(curr_step), avatar_radius);
this.invalidate();
}
private float[] moveToStep(int step) {
float[] offset = new float[2];
offset[0] = step * side_length - avatar_radius;
offset[1] = view_height - (step - 1) * side_length - avatar_radius;
Log.i("StaircaseView", "Moving avatar to "+ String.valueOf(offset[0]) + ", " + String.valueOf(offset[1]));
return offset;
}
// Setup paint with color and stroke styles
private void setupPaint() {
DrawPaint = new Paint();
DrawPaint.setColor(paint_color);
DrawPaint.setAntiAlias(true);
DrawPaint.setStrokeWidth(5);
DrawPaint.setStyle(Paint.Style.STROKE);
DrawPaint.setStrokeJoin(Paint.Join.ROUND);
DrawPaint.setStrokeCap(Paint.Cap.ROUND);
}
private Path createStaircase(float padding) {
if (constrainer == 'w') {
side_length = (view_size-padding)/(STEPS+1);
} else {
side_length = (view_size-padding)/(STEPS+1);
}
Path path = new Path();
float curr_x = 0;
float curr_y = 0;
path.moveTo(curr_x, curr_y);
curr_x += side_length;
path.lineTo(curr_x, curr_y);
for(int i=0; i<STEPS; i++) {
curr_y -= side_length;
path.lineTo(curr_x, curr_y);
curr_x += side_length;
path.lineTo(curr_x, curr_y);
}
path.lineTo(curr_x, 0);
path.lineTo(0, 0);
return path;
}
private Path createShape(ArrayList<PointF> points) {
Path path = new Path();
path.moveTo(points.get(0).x, points.get(0).y);
for(int i=1; i< points.size(); i++) {
path.lineTo(points.get(i).x, points.get(i).y);
}
return path;
}
private Path createAvatar(float[] offset, float radius){
float x = offset[0];
float y = offset[1];
Path avatar = new Path();
float width = radius*2;
avatar.addCircle(x, y, radius, Path.Direction.CW);
avatar.addCircle(x - (radius /2), y - (radius / 5), radius/5, Path.Direction.CW);
avatar.addCircle(x + (radius / 2), y - (radius / 5), radius / 5, Path.Direction.CW);
avatar.addRect((float) x - (radius / 5), (float) y - (radius / 5), (float) x + (radius / 5), (float) y - (radius/5), Path.Direction.CCW);
return avatar;
}
}
I want to move an object in a fixed circular path like the planets around the sun.
Here is the link as per my research:
how to make an object move in circular path?
However, it is not working.
Here is the updated code:
public class MainActivity extends Activity {
ourView v;
Bitmap tball;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
v = new ourView(this);
setContentView(v);
tball = (BitmapFactory.decodeResource(getResources(),
R.drawable.blueball));
}
protected void onPause() {
super.onPause();
v.pause();
}
protected void onResume() {
super.onResume();
v.resume();
}
public class ourView extends SurfaceView implements Runnable {
Thread t;
SurfaceHolder holder;
Boolean isitok = false;
public ourView(Context context) {
super(context);
// TODO Auto-generated constructor stub
holder = getHolder();
}
// float aX, aY;
#Override
public void run() {
// TODO Auto-generated method stub
while (isitok == true) {
// perform drawing
if (!holder.getSurface().isValid()) {
continue;
}
Canvas canvas = holder.lockCanvas();
canvas.drawARGB(255, 150, 150, 10);
// System.out.println("Canvas matrix -" + canvas.getm));
Paint p = new Paint();
// canvas.drawBitmap(tball, (x - tball.getWidth()) / 2,
// (y - tball.getHeight()) / 2, p);
p.setStyle(Paint.Style.STROKE);
p.setColor(Color.WHITE);
p.setColor(Color.parseColor("#0101DF"));
canvas.drawCircle(canvas.getWidth() / 2,
canvas.getHeight() / 2, 110, p);
canvas.drawCircle(canvas.getWidth() / 2,
canvas.getHeight() / 2, 210, p);
float x = (canvas.getWidth() / 2) - (tball.getWidth() / 2);
float y = (canvas.getHeight() / 2) - 210 + (210 - 110) / 2
- (tball.getHeight() / 2);
float MX = canvas.getWidth() / 2;
float MY = canvas.getHeight() / 2;
for (double i = 0; i < 12 ; i++) {
float R = 160;
x = (float) (MX + (R * Math.cos(i)));
y = (float) (MY + (R * Math.sin(i)));
//y = (float) (MY - (R * Math.sin( i )));
canvas.drawBitmap(tball, x, y, p);
i++;
}
// float movingpts[];
holder.unlockCanvasAndPost(canvas);
}
}
public void pause() {
isitok = false;
while (true) {
try {
t.join();
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
break;
}
t = null;
}
public void resume() {
isitok = true;
t = new Thread(this);
t.start();
}
}
}"
`
Take a look at NineOldAndroids. This is an animation library available on every android version back to 1.0. I don't know if its possible to achieve an fixed circular path animation but the library seems pretty powerful.
Here is the sample code for a path animation: link-to-sample
I have window with 3 circles, they are rotating simultaneously. Everything is good until a Add text to the circles, then the rotation starts to lagging.
How can i optimize drawing on canvas ?
This is my code:
#Override
protected void onDraw(final Canvas canvas) {
if (mPaint == null) {
mPaint = new Paint();
mPaint.setTextSize(20f);
}
drawUpperCircle(canvas);
drawBottomCircle(canvas);
drawMainCircle(canvas);
try {
Thread.sleep(1, 1);
invalidate();
mRotation += 0.9;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
super.onDraw(canvas);
}
private void drawUpperCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mUpperCircleCentr);
mPaint.setColor(Color.CYAN);
canvas.drawCircle(0, mUpperCircleCentr, mUpperCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mUpperCircleCentr);
canvas.drawLine(0, mUpperCircleCentr, mUpperCirclRadius, mUpperCircleCentr, mPaint);
// canvas.drawText("my text" + String.valueOf(i), mUpperCirclRadius * 2 / 3, mUpperCircleCentr - 4, mPaint);
}
canvas.restore();
}
private void drawBottomCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mBottomCircleCentr);
mPaint.setColor(Color.RED);
canvas.drawCircle(0, mBottomCircleCentr, mBottomCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mBottomCircleCentr);
canvas.drawLine(0, mBottomCircleCentr, mBottomCirclRadius, mBottomCircleCentr, mPaint);
// canvas.drawText("my text" + String.valueOf(i), mBottomCirclRadius * 2 / 3, mBottomCircleCentr - 4, mPaint);
}
canvas.restore();
}
private void drawMainCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mMainCircleCentr);
mPaint.setColor(Color.argb(100, 100, 100, 100));
canvas.drawCircle(0, mMainCircleCentr, mMainCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mMainCircleCentr);
canvas.drawLine(0, mMainCircleCentr, mMainCirclRadius, mMainCircleCentr, mPaint);
canvas.drawText("my text" + String.valueOf(i), mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, mPaint);
}
canvas.restore();
}
EDIT
To improve performance and remove drawing from UI thread I have Used Double Buffering With SurfaceView and implement #Morgans optimizations. That is how it realized.
DrawView.java
public class DrawView extends SurfaceView implements SurfaceHolder.Callback {
...............................................................
public DrawView(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float currentX = event.getX();
float currentY = event.getY();
float deltaX, deltaY;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
// Modify rotational angles according to movement
deltaX = currentX - previousX;
deltaY = currentY - previousY;
mDrawThread.mRotation += deltaY * 180 / getHeight();
}
// Save current x, y
previousX = currentX;
previousY = currentY;
return true; // Event handled
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mDrawThread = new DrawThread(getHolder(), this);
mDrawThread.setRunning(true);
mDrawThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
boolean retry = true;
mDrawThread.setRunning(false);
while (retry) {
try {
mDrawThread.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
And the main work is done in the DrawThread.java
public class DrawThread extends Thread {
private ArrayList<Path> mMainCirclePaths = new ArrayList<Path>(SEG_COUNT);
private ArrayList<Path> mUpperCirclePaths = new ArrayList<Path>(SEG_COUNT);
private ArrayList<Path> mCenterCirclePaths = new ArrayList<Path>(SEG_COUNT);
private ArrayList<Path> mBottomCirclePaths = new ArrayList<Path>(SEG_COUNT);
private boolean mRun = false;
private SurfaceHolder mSurfaceHolder;
private DrawView mDrawView;
private Paint mPaint;
private CirclesModel mCirclesModel;
public float mRotation = 0;
public DrawThread(SurfaceHolder surfaceHolder, DrawView drawView) {
mSurfaceHolder = surfaceHolder;
mDrawView = drawView;
mCirclesModel = new CirclesModel(mDrawView.getHeight());
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextSize(18f);
initPaths();
}
public void setRunning(boolean b) {
mRun = b;
}
#Override
public void run() {
while (mRun) {
Canvas canvas = null;
try {
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
drawMainCircle(canvas);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y],
mCirclesModel.mSmallCirclesRadius, mPaint);
drawCenterCircle(canvas);
drawUpperCircle(canvas);
drawBottomCircle(canvas);
//mRotation += 0.5f;
}
} finally {
if (canvas != null) {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
private void drawMainCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y]);
float rot = mRotation;
mPaint.setColor(Color.LTGRAY/* argb(100, 255, 255, 255) */);
canvas.drawCircle(mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y],
mCirclesModel.mBigCirclesRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y]);
rot += SEG_IN_GRAD;
float absRot = Math.abs(rot % 360);
if (absRot > mCirclesModel.mMainCircleSegment[0] && absRot < mCirclesModel.mMainCircleSegment[1]) {
continue;
}
canvas.drawLine(mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y],
mCirclesModel.mBigCirclesRadius, mCirclesModel.mMainCircleCentr[CirclesModel.Y], mPaint);
canvas.drawPath(mMainCirclePaths.get(i), mPaint);
// canvas.drawText("my text" + String.valueOf(i),
// mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, mPaint);
}
canvas.restore();
}
.................................................................
}
Double Buffering is implemented in the two lines of code
canvas = mSurfaceHolder.lockCanvas(null); here I take from surface view canvas in which i will draw next frame.
mSurfaceHolder.unlockCanvasAndPost(canvas); here I am overlaping current image on SurfaceView with new canwas, this is the moment where image changes. Be aware if you have transparent elements then the previous image will be still visible, Image is not replaced, but overlaped.
Below is a version of your code that contains a few optimizations.
First, I try not to draw the lines and text that currently offscreen. I do this by tracking the rotation angle, and skipping the drawing for net rotations between 90 and 270 degrees. On my 2.3 simulator this improved performance overall by 25%.
Second, I "cache" the strings I am going to draw by initializing an array (ArrayList<Path>) with one Path for each string I need to draw. I do this in the same place you were one-time initializing the mPaint. Then I draw the strings using canvas.drawPath(...). On my 2.3 simulator this improved performance by another 33%. The net effect was to about double the rotation speed. Also, it stopped the text from "wiggling around".
A few other notes:
I removed the Thread.sleep(1,1). Not sure exactly what you were trying to accomplish with that.
I changed rotation delta to 1.0 from 0.9. Not sure why you were using 0.9. Note that if you change to back, my "log time it takes to rotate 10 degrees" will not quite work since mRotation % 10 may seldom be 0.
On a 4.1 simulator, the rotation was generally much faster (about 4x) than on my 2.3 simulator. And a 4.1 device was faster yet.
public class AnimView extends View {
Paint mPaint;
ArrayList<Path> mTextPaths;
float mRotation = 0f;
float mUpperCircleCentr = 150f;
float mUpperCirclRadius = 150f;
private static final int SEG_COUNT = 60;
private static final float SEG_IN_GRAD = 360.0f / SEG_COUNT;
float mBottomCircleCentr = 450f;
float mBottomCirclRadius = 150f;
float mMainCircleCentr = 300f;
float mMainCirclRadius = 300f;
long mLastMillis = 0L;
// ctors removed
#Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
if (mPaint == null) {
mPaint = new Paint();
mPaint.setTextSize(20f);
// init text paths
mTextPaths = new ArrayList<Path>(SEG_COUNT);
for (int i = 0; i < SEG_COUNT; i++) {
Path path = new Path();
String s = "my text" + String.valueOf(i);
mPaint.getTextPath(s, 0, s.length(), mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, path);
path.close(); // not required on 2.2/2.3 devices
mTextPaths.add(path);
}
}
if (mLastMillis == 0L) {
mLastMillis = System.currentTimeMillis();
}
drawUpperCircle(canvas);
drawBottomCircle(canvas);
drawMainCircle(canvas);
invalidate();
if (((int) mRotation) % 10 == 0) {
long millis = System.currentTimeMillis();
Log.w("AnimateCanvas", "OnDraw called with mRotation == " + mRotation);
Log.w("AnimateCanvas", "Last 10 degrees took millis: " + (millis - mLastMillis));
mLastMillis = millis;
}
}
private void drawUpperCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mUpperCircleCentr);
float rot = mRotation;
mPaint.setColor(Color.CYAN);
canvas.drawCircle(0, mUpperCircleCentr, mUpperCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mUpperCircleCentr);
rot += SEG_IN_GRAD;
if (rot % 360 > 90 && rot % 360 < 270)
continue;
canvas.drawLine(0, mUpperCircleCentr, mUpperCirclRadius, mUpperCircleCentr, mPaint);
}
canvas.restore();
}
private void drawBottomCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mBottomCircleCentr);
float rot = mRotation;
mPaint.setColor(Color.RED);
canvas.drawCircle(0, mBottomCircleCentr, mBottomCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mBottomCircleCentr);
rot += SEG_IN_GRAD;
if (rot % 360 > 90 && rot % 360 < 270)
continue;
canvas.drawLine(0, mBottomCircleCentr, mBottomCirclRadius, mBottomCircleCentr, mPaint);
}
canvas.restore();
}
private void drawMainCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mMainCircleCentr);
float rot = mRotation;
mPaint.setColor(Color.argb(100, 100, 100, 100));
canvas.drawCircle(0, mMainCircleCentr, mMainCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mMainCircleCentr);
rot += SEG_IN_GRAD;
if (rot % 360 > 90 && rot % 360 < 270)
continue;
canvas.drawLine(0, mMainCircleCentr, mMainCirclRadius, mMainCircleCentr, mPaint);
canvas.drawPath(mTextPaths.get(i), mPaint);
// canvas.drawText("my text" + String.valueOf(i), mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, mPaint);
}
canvas.restore();
}
}
Your code is pretty nice and simple. You can optimize it by using less loops for instance, drawing things all together or combining variables, but this would quickly get messy.
I would recommend you to keep your drawing code more or less equal. You actually don't do the worst thing : instanciating objects, and it's clear and easy to maintain.
But you could maybe try to use a double buffer : drawing in a buffer in ram and flipping the buffer one shot on the screen. This generally performs quite well to get a constant animation pace. Use locking and unlocking of your canvas : Double buffering in Java on Android with canvas and surfaceview
I have a little drawing app and want to use "complex" shapes as brushes, i.e. a star.
Drawing with a simple brush already works with this code:
remotePath.reset();
remotePath.moveTo(start_x, start_y);
float dx = Math.abs(end_x - start_x);
float dy = Math.abs(end_y - start_y);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
remotePath.quadTo(start_x, start_y, (end_x + start_x) / 2, (end_y + start_y) / 2);
}
remotePath.lineTo(end_x, end_y);
// commit the path to our offscreen
mCanvas.drawPath(remotePath, remotePaint);
// kill this so we don't double draw
remotePath.reset();
invalidate();
I basically want the same functionality using this bitmap:
Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.brush_star);
My solution currently is using a list of points (coordinates) to draw the bitmap. The problem with that solution is that it only draws bitmaps at the given points resulting in having gaps between each drawn bitmap. I rather would like to get a smooth line while drawing like with a simple brush without any gaps in between.
Current code for the bitmap drawing:
protected void onDraw(Canvas canvas) {
// Make canvas white
canvas.drawColor(Color.WHITE);
// Paintable area
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, mPaint);
for (Point point : points) {
canvas.drawBitmap(complexBrush, point.x, point.y, p);
}
}
What's the best way to do so?
Thanks for any help!
I use this
Point's class:
public class Point implements Serializable {
float x, y;
float dx, dy;
}
Paint object:
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
paint.setColor(Color.RED);
paint.setAntiAlias(true);
draw on canvas:
private void drawCanvas(Canvas canvas, List<Point> pts){
if (pts.size() > 1){
Path path = new Path();
final int SMOOTH_VAL = 6;
for(int i = pts.size() - 2; i < pts.size(); i++){
if(i >= 0){
Point point = pts.get(i);
if(i == 0){
Point next = pts.get(i + 1);
point.dx = ((next.x - point.x) / SMOOTH_VAL);
point.dy = ((next.y - point.y) / SMOOTH_VAL);
}
else if(i == pts.size() - 1){
Point prev = pts.get(i - 1);
point.dx = ((point.x - prev.x) / SMOOTH_VAL);
point.dy = ((point.y - prev.y) / SMOOTH_VAL);
}
else{
Point next = pts.get(i + 1);
Point prev = pts.get(i - 1);
point.dx = ((next.x - prev.x) / SMOOTH_VAL);
point.dy = ((next.y - prev.y) / SMOOTH_VAL);
}
}
}
boolean first = true;
for(int i = 0; i < pts.size(); i++){
Point point = pts.get(i);
if(first){
first = false;
path.moveTo(point.x, point.y);
}
else{
Point prev = pts.get(i - 1);
path.cubicTo(prev.x + prev.dx, prev.y + prev.dy, point.x - point.dx, point.y - point.dy, point.x, point.y);
}
}
canvas.drawPath(path, paint);
} else {
if (pts.size() == 1) {
Point point = pts.get(0);
canvas.drawCircle(point.x, point.y, 2, paint);
}
}
}
Draw on bitmap canvas:
private void drawBitmap(Bitmap bmp, List<Point> pts) {
Canvas c = new Canvas(bmp);
drawCanvas(c, pts);
}