I'm making a custom slide control. Is like a Volume wheel, so some values augment or decrease depending on rotate direction.
I have two issues with this:
The performance is really low
The garbage collector is trigger, many many times.
Well, I'm sure that i'm doing something wrong, so please give me a Light.
Am working with Android graphics on 2.1 (eclaire) SDK.
This is the code of the view that am calling from my activity:
public class DrawingView extends View {
private Paint p;
Bitmap bitmap;
Context mContext;
Canvas canvas;
private float sweepAngle;
private int _height;
private int _width;
private float lastAngle;
private int percent;
public DrawingView(Context context) {
super(context);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
this.set_Width(display.getWidth());
this.set_Height(display.getHeight());
this.setSweepAngle(10);
mContext = context;
p = new Paint();
p.setAntiAlias(true);
}
protected int getAngleFromLocation(Point location){
int finalAngle = (int) (Math.atan2(location.y - 200, location.x - 200) * (180 / Math.PI));
return finalAngle;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
bitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(),Bitmap.Config.ARGB_8888);
this.canvas = new Canvas(bitmap);
RectF rectF = new RectF();
rectF.set(20, 20, this.get_Widtt() - this.get_Widtt()/10, this.get_Widtt() - this.get_Widtt()/10);
canvas.drawArc(rectF, 180, this.getSweepAngle(), true, p);
//invalidate();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
double increment = 3.6;
Point touchLocation = new Point();
touchLocation.x = (int)event.getX();
touchLocation.y = (int)event.getY();
canvas.drawBitmap(bitmap = Bitmap.createBitmap(canvas.getWidth(),canvas.getHeight(),Bitmap.Config.ARGB_8888),event.getX(), event.getY(),null );
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
lastAngle = this.getAngleFromLocation(touchLocation);
System.out.println("ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
System.out.println("ACTION_MOVE");
int currentAngle = this.getAngleFromLocation(touchLocation);
System.out.println("CURRENT ANGLE: " + currentAngle);
if (currentAngle > lastAngle || (currentAngle == 1 && lastAngle == 359)) {
percent += increment;
} else if (currentAngle < lastAngle) {
percent -= increment;
}
if (percent > 360) {
percent = 360;
} else if (percent < 0) {
percent = 0;
}
lastAngle = currentAngle;
this.setSweepAngle(percent);
//Write the label
//int realPercent = percent*100/360;
System.out.println("PERCENT: "+percent);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
No need to create a new bitmap on every frame. In fact you should try to avoid allocationg any objects at all in onDraw().
Try this:
private RectF mRectF = new RectF();
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
mRectF.set(20, 20, this.get_Widtt() - this.get_Widtt() / 10, this.get_Widtt() - this.get_Widtt() / 10);
canvas.drawArc(rectF, 180, this.getSweepAngle(), true, p);
}
also remove the call to canvas.drawBitmap() in onTouchEvent.
It's most likely the bitmap/canvas creation that's taking so long and triggering the GC so much. You want to do as little object creation as possible during onDraw() in particular, and onTouchEvent() as well. Is there any reason you can't move that to a separate method that gets called once and reuse the same bitmap each frame? It looks like the values you're passing to createBitmap() are static, so it shouldn't be an issue.
Related
I am just creating an application in which I need to drill into the bitmap, I succeed for that also but bitmap drill is lagging as I touch image view. Any help on this is really appreciated...Thanks..
Here is Main File
public class MainActivity extends Activity{
int x, y;
Bitmap background,foreground;
ImageView iv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.img);
background = BitmapFactory.decodeResource(getResources(), R.mipmap.mug_shot);
foreground = BitmapFactory.decodeResource(getResources(), R.mipmap.poster);
iv.setImageBitmap(combineTwoBitmaps(background, foreground));
iv.setOnTouchListener(new View.OnTouchListener()
{
#Override
public boolean onTouch(View view, MotionEvent event)
{
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE:
x = (int) (event.getRawX()+100);
y = (int) (event.getY()+100);
System.out.println("X : "+ x +" Y : "+ y);
foreground = punchAHoleInABitmap(foreground);
iv.setImageBitmap(combineTwoBitmaps(background,foreground));
System.out.println("Hello this is Test2");
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
return true;
}
});
}
private Bitmap punchAHoleInABitmap(Bitmap foreground)
{
System.out.println("Hello this is Tesyt");
Bitmap bitmap = Bitmap.createBitmap(foreground.getWidth(), foreground.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
canvas.drawBitmap(foreground, 0, 0, paint);
paint.setAntiAlias(true);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
float radius = (float)(getScreenSize().x *.35);
float x1 = (float) ((x*.5) + (radius * .5));
float y1 = (float) ((y*.5) + (radius * .5));
System.out.println("X1 : "+ x1 +" Y1 : "+ y1);
canvas.drawCircle(x1, y1, 50, paint);
return bitmap;
}
private Bitmap combineTwoBitmaps(Bitmap background, Bitmap foreground) {
Bitmap combinedBitmap = Bitmap.createBitmap(background.getWidth(), background.getHeight(), background.getConfig());
Canvas canvas = new Canvas(combinedBitmap);
Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
canvas.drawBitmap(background, 0, 0, paint);
canvas.drawBitmap(foreground, 0, 0, paint);
return combinedBitmap;
}
private Point getScreenSize() {
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size;
}
}
ACTION_MOVE is called many times when users slide the screen. This means you will create quite amount of bitmaps in short time.
With GestureDetector you can detect exact one slide/swipe/fling.
I am making an application I am unable to one issue last 3 days doing google as much possible.I make a circle on canvas and want to crop image that part and show that image in zoom mode.My first step is like in screen here:-
in this I am selecting area.here is my code used by me for this.
private float x, y;
private boolean zooming = false;
private Paint mPaint;
private Matrix mmatrix;
private Shader mShader;
private Bitmap mBitmap;
private List<Point> mpoints;
private List<MyPoints> mpointlist;
private Path mpath;
private Canvas mcanvas;
private Bitmap mresult_bitmap, resultingImage,finalbitmap;
private Context mcontext;
private boolean bfirstpoint = false;
private Point mfirstpoint = null;
private Point mlastpoint = null;
public CircularZoomView(Context context) {
super(context);
mcontext = context;
mpath = new Path();
mpoints = new ArrayList<Point>();
setBackgroundResource(R.drawable.testing);
mPaint = new Paint();
mresult_bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.testing);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setPathEffect(new DashPathEffect(new float[] { 10, 20 }, 0));
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.RED);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (zooming && resultingImage!=null) {
zooming = false;
ShowImage(resultingImage);
canvas.drawBitmap(resultingImage,mmatrix, null);
}
boolean first = true;
for (int i = 0; i < mpoints.size(); i += 2) {
Point point = mpoints.get(i);
if (first) {
first = false;
mpath.moveTo(point.x, point.y);
} else if (i < mpoints.size() - 1) {
Point next = mpoints.get(i + 1);
mpath.quadTo(point.x, point.y, next.x, next.y);
} else {
mlastpoint = mpoints.get(i);
mpath.lineTo(point.x, point.y);
}
}
canvas.drawPath(mpath, mPaint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
x = event.getX();
y = event.getY();
Point point = new Point();
point.x = (int) event.getX();
point.y = (int) event.getY();
if (bfirstpoint) {
if (comparepoint(mfirstpoint, point)) {
mpoints.add(mfirstpoint);
addCircleFromPath(mpath);
} else {
mpoints.add(point);
}
} else {
mpoints.add(point);
}
if (!(bfirstpoint)) {
mfirstpoint = point;
bfirstpoint = true;
}
invalidate();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
zooming = false;
this.invalidate();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
zooming = true;
mlastpoint = point;
if (mpoints.size() > 12) {
if (!comparepoint(mfirstpoint, mlastpoint)) {
mpoints.add(mfirstpoint);
addCircleFromPath(mpath);
}
}
this.invalidate();
break;
default:
break;
}
return true;
}
public Bitmap getCroppedBitmap(Bitmap bitmap) {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
bitmap.getWidth() / 2, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
void ShowImage(Bitmap mbitmap) {
Display display = ((MainActivity) mcontext).getWindowManager().getDefaultDisplay();
int screenWidth = display.getWidth();
float imageWidth = (float)mbitmap.getWidth();
float imageHeight = (float)mbitmap.getHeight();
float newHeight = imageHeight / (imageWidth / screenWidth);
float newWidth = screenWidth;
float scaleWidth = screenWidth / imageWidth;
float scaleHeight = newHeight / imageHeight;
SetImageMatrix(mbitmap,scaleWidth,scaleHeight);
}
void SetImageMatrix(Bitmap image,float scaleWidth, float scaleHeight) {
mmatrix = new Matrix();
mmatrix.setTranslate(40,40);
mmatrix.postScale(scaleWidth/2, scaleHeight/2);
/*image.setImageMatrix(mmatrix);
image.setScaleType(ScaleType.MATRIX);
image.invalidate();*/
}
private boolean comparepoint(Point first, Point current) {
int left_range_x = (int) (current.x - 3);
int left_range_y = (int) (current.y - 3);
int right_range_x = (int) (current.x + 3);
int right_range_y = (int) (current.y + 3);
if ((left_range_x < first.x && first.x < right_range_x)
&& (left_range_y < first.y && first.y < right_range_y)) {
if (mpoints.size() < 10) {
return false;
} else {
return true;
}
} else {
return false;
}
}
private void addCircleFromPath(Path path){
RectF bounds = new RectF();
path.computeBounds(bounds, true);
int width = (int) (bounds.right-bounds.left);
int height = (int) (bounds.bottom-bounds.top);
if(width<20 && height<20){
path.reset();
return;
}
int radius ;
if(width>=height)
radius = Math.round(((width/2)));
else radius = Math.round((int) ((height/2)));
/*CircleTagObject circle = new CircleTagObject((int)bounds.left+width/2, (int)bounds.top+height/2, radius, crossBitmap, tagBitmap,circleArray.size(),
ImageEditorView.this);
circleArray.add(circle);
tagBallID = circleArray.size() - 1;
dragEnable = true;*/
resultingImage = getCroppedBitmap(Bitmap.createBitmap(mresult_bitmap,0,0,200,200));
mcanvas = new Canvas(resultingImage);
path.reset();
resetView();
invalidate();
}
public void resetView() {
mpoints.clear();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setPathEffect(new DashPathEffect(new float[] { 10, 20 }, 0));
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.RED);
invalidate();
}
If I create hard coded bitmap like above its showing good but not crop bitmap of selected part.Like this image.
but when add exact coordinate of selected area Like as:-
resultingImage = getCroppedBitmap(Bitmap.createBitmap(mresult_bitmap,(int)bounds.left,(int)bounds.top,width,height));
Then exception occure:-
07-12 10:58:56.700: E/MessageQueue-JNI(12310): java.lang.IllegalArgumentException: y + height must be <= bitmap.height()
07-12 10:58:56.700: E/MessageQueue-JNI(12310): at android.graphics.Bitmap.createBitmap(Bitmap.java:565)
07-12 10:58:56.700: E/MessageQueue-JNI(12310): at android.graphics.Bitmap.createBitmap(Bitmap.java:530)
07-12 10:58:56.700: E/MessageQueue-JNI(12310): at com.intel.view.CircularZoomView.addCircleFromPath(CircularZoomView.java:237)
I know why This exception occurs but unable to find solution how crop image of selected part.Thanks in advance.
I know its too late for your solution but this may help to others Use of this code
help you to come out from this problem.
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 am currently trying to create a meter that can be adjusted in the percentage of fill. The problem I have is I'm not good at math at all. I want to start drawing an arc in the 'north' (first image), as opposed to a normal arc having its 0 deg point in the 'east' (as shown in second image).
I want to be able to increase the blue area in image 1 in size (angle) by dragging/touching it along the screen. Now these are things I am able to do in some kind of fashion now. The real problem I am facing is this:
I use the following code to draw the blue area:
mStart = -90;
int degree = (int)((theta + Math.PI) * 180 / Math.PI);
mSweep = degree;
RectF mOvals = new RectF(c.x - outerRadius + circleThickness, c.y - outerRadius + circleThickness, c.x + outerRadius - circleThickness, c.y + outerRadius - circleThickness );
mArcSetLevel = new Path();
if(mArcSetLevel != null ) {
canvas.drawArc(mOvals, mStart, mSweep, true, arcPaint);
}
Setting the start at -90 makes it start 90 deg earlier. To track the angle of the touch I use this formula, but this is where it goes wrong:
int py = (int)event.getY() - c.y;
int px = (int)event.getX() - c.x;
theta = (float) ((float) Math.atan2(py, px) - (Math.PI / 2)); // - Math.PI / 2 to correct -90 start
When I go further than exactly 270 degrees the blue area gets reset and draws itself from north to west in a much smaller angle (because of the 'false' start of -90, shown in third image). My math skills are simply not good enough for me to be able to solve this, although I can think of why it is happening I cannot seem to find the solution.
The (very messy) code to the entire view I made is as follows:
private Canvas canvas;
//Canvas width and height
private int h = -1;
private int w = -1;
//circle properties
private Paint paint;
private Paint arcPaint;
private Path circle;
private Point c;
private int outerRadius;
private int circleThickness = 20;
//point click in wheel
private float theta = 0;
private float mStart;
private float mSweep;
private Paint mBgPaints = new Paint();
private Path mArcSetLevel;
int padding = 10;
OnMeterWheelChangeListener onMeterWheelChangeListener = null;
public MeterWheel(Context context){
super(context);
initCircleSeekBar();
}
public MeterWheel(Context context, AttributeSet attrs) {
super(context, attrs);
initCircleSeekBar();
}
private void initCircleSeekBar() {
canvas = new Canvas();
circle = new Path();
paint = new Paint();
arcPaint = new Paint();
c = new Point();
mBgPaints.setAntiAlias(true);
mBgPaints.setStyle(Paint.Style.FILL);
mBgPaints.setColor(0x88FF0000);
mBgPaints.setStrokeWidth(0.5f);
mArcSetLevel = new Path();
this.draw(canvas);
}
#Override
protected void onSizeChanged(int width, int height, int oldw, int oldh) {
// TODO Auto-generated method stub
super.onSizeChanged(width, height, oldw, oldh);
w = width;
h = height;
Log.i("POWERWHEEL", String.valueOf(w) + " " + String.valueOf(h));
c.set(w/2, h/2);
drawCircle();
}
private void drawCircle() {
outerRadius = Math.min(h,w)/2;
circleThickness = (int) (outerRadius*0.15);
circle.addArc(new RectF(c.x - outerRadius + circleThickness/2, c.y - outerRadius + circleThickness/2, c.x + outerRadius - circleThickness/2, c.y + outerRadius - circleThickness/2 ), 0, 360);
circle.moveTo(c.x, c.y);
//paint.setShader(new SweepGradient(w/2,h/2, colourarry, null));
paint.setColor(Color.GRAY);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(circleThickness);
paint.setAntiAlias(true);
arcPaint.setColor(Color.BLUE);
arcPaint.setStyle(Style.FILL);
arcPaint.setStrokeWidth(circleThickness);
arcPaint.setAntiAlias(true);
}
#SuppressLint("DrawAllocation")
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
if(circle != null){
//draw circle
canvas.drawPath(circle, paint);
mStart = -90;
int degree = (int)((theta + Math.PI) * 180 / Math.PI);
Log.d("POWERWHEEL", "" + degree);
mSweep = degree;
RectF mOvals = new RectF(c.x - outerRadius + circleThickness, c.y - outerRadius + circleThickness, c.x + outerRadius - circleThickness, c.y + outerRadius - circleThickness );
mArcSetLevel = new Path();
if(mArcSetLevel != null ) {
canvas.drawArc(mOvals, mStart, mSweep, true, arcPaint);
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setPressed(true);
onStartTrackingTouch(event);
trackTouchEvent(event);
break;
case MotionEvent.ACTION_MOVE:
trackTouchEvent(event);
break;
case MotionEvent.ACTION_UP:
trackTouchEvent(event);
onStopTrackingTouch();
setPressed(false);
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
onStopTrackingTouch();
setPressed(false);
invalidate();
break;
}
return true;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width,height);
}
private void onStartTrackingTouch(MotionEvent event) {
}
private void onStopTrackingTouch() {
}
private void trackTouchEvent(MotionEvent event) {
int py = (int)event.getY() - c.y;
int px = (int)event.getX() - c.x;
theta = (float) ((float) Math.atan2(py, px) - (Math.PI / 2));
Log.d("POWERWHEEL", "theta: " + theta);
this.invalidate();
}
public void setSize(int x, int y){
h = y;
w = x;
}
public void setCirleThickness(int t){
circleThickness = t;
}
public void setOnMeterWheelChangeListener (OnMeterWheelChangeListener listener) {
onMeterWheelChangeListener = listener;
}
public interface OnMeterWheelChangeListener{
public void onStartTrackingTouch (MeterWheel colourWheel);
public void onStopTrackingTouch (MeterWheel colourWheel);
}
Thanks a million in advance!
When calculating theta, you use atan2 which returns the angle in +/- pi. So when being in the upper left quadrant it will return a value in the range -pi/2 to -pi (asuming y is positive downwards and x is positve rightwards). You substract pi/2 directly with gives a range of -pi to -3pi/2. In onDraw you then add pi again (confusing) giving a range of 0 to -pi/2 of the sweep for this quadrant. This means it will paint the arc 0 to pi/2 (or 0 to 90 degrees) counterclockwise from your starting position at the top. You must make sure your sweep always keeps in the range 0 to pi. Nicest solution is to shift the coordinates by -pi/2, so that instead of Math.atan2(py, px), you do Math.atan2(px, -py) and then if theta is negative you add 2*pi. Something like (I don't write android)
theta = (float) Math.atan2(px, -py);
if (theta < 0) theta += 2 * Math.PI;
and then in onDraw
int degree = (int)(theta * 180 / Math.PI);
Log.d("POWERWHEEL", "" + degree);
mSweep = degree;
If you are still experiencing problems check that mSweep is always in the range 0 to 360 degrees.
How do you fix the position of an image drawn onto a canvas with a large scrollable background?
picture this:
the background image is that of a room, the object images are bed, door etc.
the object images are drawn on top of the background.
when i scroll the background, the objects should move with respect to the background image, correct?
the problem is the object images also move but the position doesn't stay the same, i.e they shift from their original positions.
here is my full class implementation.
Bitmap bmImage;
SpriteAnim8 anim;
MThread thread;
PersonAnimated person, person2;
Canvas canvas = new Canvas();
Rect displayRect = null;
Rect scrollRect = null;
int scrollRectX = 0, scrollRectY = 0;
float scrollByX = 0, scrollByY = 0;
float startX = 0, startY = 0;
int initX = 200, initY = 200;
float a = initX, b = initY;
public MGamePanel(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// create Person and load bitmap
person = new PersonAnimated(BitmapFactory.decodeResource(getResources(), R.drawable.dad_anim),
10, 200 /* initial position */,
45, 56 /* width and height of sprite */,
5, 10); /* FPS and number of frames in the animation */
// Destination rect for our main canvas draw
displayRect = new Rect(0, 0, SpriteAnim8.displayWidth, SpriteAnim8.displayHeight);
// Scroll rect: this will be used to 'scroll around' over the bitmap
scrollRect = new Rect(0, 0, SpriteAnim8.displayWidth, SpriteAnim8.displayHeight);
// Load a large bitmap
bmImage = BitmapFactory.decodeResource(getResources(), R.drawable.l1_plain);
// create the game loop thread
thread = new MThread(getHolder(), this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// at this point the surface is created and we can safely start the game
// loop
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Surface is being destroyed");
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
while (retry) {
try {
thread.setRunning(false);
((Activity) getContext()).finish();
retry = false;
} catch (Exception e) {
// try again shutting down the thread
}
}
Log.d(TAG, "Thread was shut down cleanly");
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// delegating event handling to the person
startX = event.getRawX();
startY = event.getRawY();
// }
case MotionEvent.ACTION_MOVE:
float x = event.getRawX();
float y = event.getRawY();
// Calculate move update.
mScrollByX = x - mStartX + mOldScrollByX; // move update x increment
mScrollByY = y - mStartY + mOldScrollByX; // move update y increment
onDraw(canvas);
break;
case MotionEvent.ACTION_UP:
mOldScrollByX = mScrollByX;
mOldScrollByY = mScrollByY;
break;
}
return true;
}
public void render(Canvas canvas) {
Paint paint = new Paint();
canvas.drawBitmap(bmImage, scrollRect, displayRect, paint);
person.draw(canvas);
}
#Override
protected void onDraw(Canvas canvas) {
int newScrollRectX = scrollRectX - (int) scrollByX;
int newScrollRectY = scrollRectY - (int) scrollByY;
// Prevent scrolling off the left or right edges of the bitmap.
if (newScrollRectX < 0) {
newScrollRectX = 0;
} else if (newScrollRectX > (bmImage.getWidth() - SpriteAnim8.displayWidth)) {
newScrollRectX = (bmImage.getWidth() - SpriteAnim8.displayWidth);
}
// Prevent scrolling off the top or bottom edges of the bitmap.
if (newScrollRectY < 0) {
newScrollRectY = 0;
} else if (newScrollRectY > (bmImage.getHeight() - SpriteAnim8.displayHeight)) {
newScrollRectY = (bmImage.getHeight() - SpriteAnim8.displayHeight);
}
// set the updated scroll rect coordinates.
scrollRect.set(newScrollRectX, newScrollRectY, newScrollRectX
+ SpriteAnim8.displayWidth, newScrollRectY
+ SpriteAnim8.displayHeight);
// Reset current scroll coordinates to reflect the latest updates so we
// can repeat
scrollRectX = newScrollRectX;
scrollRectY = newScrollRectY;
person.setX(person.getX() + scrollByX);
person.setY(person.getY() + scrollByY);
}
is this correct?
Here is a code how to scroll a picture/image which is drawn on canvas:
//define points on the custome view class level
PointF touchStart = new PointF();
PointF picStart = new PointF();
PointF prevPicStart = new PointF();
Handle the touch and remember the previous poistion of the picture
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
touchStart.set(ev.getX(), ev.getY());
break;
}
case MotionEvent.ACTION_MOVE: {
float newX = ev.getX() - touchStart.x + prevPicStart.x;
float newY = ev.getY() - touchStart.y + prevPicStart.y;
//assuming the the picture is bigger than the screen
if ((newX <= 0 && newX > 0 - pic.getWidth() + screenW)){
picStart.x = newX;
}
if ((newY <= 0 && newY > 0 - pic.getHeight() + screenH)){
picStart.y = newY;
}
invalidate();
break;
}
case MotionEvent.ACTION_UP:
prevPicStart.x = picStart.x;
prevPicStart.y = picStart.y;
break;
}
return true;
}
In onDraw
canvas.drawBitmap(pic, picStart.x, picStart.y, null);
Not sure about your code, but you can use a FrameLayout with background that you want for room and add ScrollView as its child, which will contain the other objects which are scrollable.
HTH !