I'm making a graphing library in Android and am aware that if I update the canvas by calling invalidate it can cause a heap memory problem in some devices especially when the user is using a Relative Layout and has some views loading or has multiple graphs stacked on each other. So what I want to do is change the graph of the view without android having to redraw all other views on the Relative Layout.
The only solution I have come across is adding a boolean system to the custom view's on draw to check if the same view requested the on draw, otherwise it would keep the same canvas. However, this will only prevent the same view from being redrawn again and will also draw the other views when it is drawn.
Here is the solution mentioned above.
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(cropx != 0){
croppedBmp = Bitmap.createBitmap(drawGraph2, 0, 0, (drawGraph2.getWidth()*cropx/100), drawGraph2.getHeight());
canvas.drawBitmap(croppedBmp, 0, 0, new Paint());
croppedBmp.recycle();
System.gc();
Runtime.getRuntime().gc();
}
status = false;
}
public void plotPoints(ArrayList<Double> yvalue, ArrayList<Double> xpoint){
drawpath2 = new Path();
drawCanvas = new Canvas(drawGraph2);
drawCanvas.scale(1, -1);
boolean first = false;
for(int i = 0; i< xpoint.size(); i++){
if(!Double.isNaN(yvalue.get(i))){
if(first == false){
drawpath2.moveTo(xpoint.get(i).floatValue(),yvalue.get(i).floatValue());
first = true;
}else if(i==xpoint.size()-1){
drawCanvas.drawPath(drawpath2, paint);
drawpath2.reset();
}else{
drawpath2.lineTo(xpoint.get(i).floatValue(), yvalue.get(i).floatValue());
}
}
}
Canvas singleUseCanvas = new Canvas(drawGraph2);
singleUseCanvas.drawPath(drawpath, paint);
status = true;
invalidate();
}
Related
When using invalidate the history of previous drawing actions are gone. I want to create an animation where I draw some new pixels after a certain delay.
I have tried using a Bitmap to remember the previous set pixels, but it won't load on the canvas, it throws an exception, see code below (commented not working).
I tried using invalidate but it completely wipes out the previous set pixels.
#Override
protected void onDraw(Canvas canvas) {
int colorWhite = getResources().getColor(android.R.color.white);
int colorBlack = getResources().getColor(android.R.color.black);
//not working
//Bitmap bmp = Bitmap.createBitmap(300,300, Bitmap.Config.ARGB_8888);
//canvas.setBitmap(bmp);
try
{
if(board == null) {
Ant ant = new Ant(new Position(430,430), new TurnDegree(0));
board = new Board(ant, 15000);
board.moveAnt();
}
MarkedPosition position = board.positions().get(count);
if(position.isMarked()) {
paint.setColor(colorWhite);
} else {
paint.setColor(colorBlack);
}
paint.setStrokeWidth(5);
System.out.println(position.position().x());
canvas.drawRect(position.position().x(), position.position().y(),
position.position().x() + 5, position.position().y() + 5, paint);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
invalidate();
}
}, 100);
}
catch(Exception e){
System.out.println("Error");
}
count++;
}
How can I create a sort of animation which draws new pixels to the screen after a certain delay, without having to repaint all pixels which I set previously?
you can store your previous pixels in ArrayList. after invalidate redraw all the points from ArrayList
make Arraylist of rect
ArrayList<Rect> allRects = new ArraList();
add when you draw rect on cavas
Rect rect = new Rect(position.position().x(), position.position().y(),
position.position().x() + 5, position.position().y() + 5);
canvas.drawRect(rect);
allRects.add(rect);
after invalidate again draw all rect using for loop
for (i = 0; i< allRects.size(); i++){
canvas.drawRect(allRects.get(i));
}
I used the fingerpaint demo and modify it for multi touch. This part is all fine and the path get drawn as expected. Now, I need to stored these path into an ArrayList so when I swipe each View in viewpager I can keep the state and redrawn those path stored in memory.
This is my code to draw canvas.
protected override void OnDraw (Canvas canvas)
{
base.OnDraw (canvas);
canvas.DrawBitmap (mBitmap, 0, 0, mBitmapPaint);
if (Strokes != null && Strokes.Count > 0)
{
for (int i = 0; i < Strokes.Count; i++) {
var path = (Path) Strokes[i].Path;
canvas.DrawPath (path, Strokes[i].Pen);
}
}
}
The part I passed the Stroke List :
viewPagerAdapter.InitializedView += (object sender, PreviewPagerViewEventArgs e) => {
if (mPageData.Count <= e.Position)
mPageData.Add (e.Data);
else {
var view = (DrawableView) GetVisibleView (e.Position);
view.Background = new ColorDrawable(Android.Graphics.Color.Blue);
view.Strokes = (List<StrokeData>) mPageData [e.Position];
view.Invalidate();
}
};
I placed a bullet point and the canvas.DrawPath did get through but the canvas still not get drawn
Hi I am working on plotting a real time graph of incoming signals using SurfaceView.
The sampling rate is 128Hz and the target graph refresh rate is 50Zh.
Things run pretty smoothly, the points are drawn real-time properly.
I plot the data in segments of a few points using Path()
for each segment I call path.computeBounds() to get a rect that I will use to call holder.lockCanvas(rect) and draw the path. Using a rect prevents flickering and reduces cpu usage
when the graph reaches the end I lock the entire canvas and clear the background, draw the graph frame and then continue on plotting.
the problem is that at the beginning of each new "page" I get a ghost image from the last page:
I believe this is caused by double buffering / use of a dirty area when plotting.
I have looked for solutions to this problem but none seem adequate for this type of application. Any help is most welcome.
Thanks
Jean-Pierre
Code follows:
private void draw() {
Point point = null;
Canvas canvas = null;
Path path = new Path();
ArrayList<Point> pointArray;
float oldX = -1;
boolean setToClear = false;
boolean isNewSegment = false;
if (samplesInQueue == 0) {
return;
}
pointArray = new ArrayList<Point>((int) samplesInQueue);
for (int i = 0; i < samplesInQueue; i++) {
// take a peek at the point without retrieving it from the point
// queue
point = Points.peek();
// check if first point of segment is the start of a page
if (i == 0) {
if (lastSegmentEndPoint != null) {
if (point.x < lastSegmentEndPoint.x) {
// yes then we will need to clear the screen now
isNewSegment = true;
}
} else {
// yes then we will need to clear the screen now
isNewSegment = true;
}
}
if (point != null) {
if (point.x > oldX) {
// put consecutive points in the path point array
point = Points.poll();
samplesInQueue--;
pointArray.add(point);
oldX = point.x;
} else {
// we have a wrap around, stop and indicate we need to clear
// the screen on the next pass
if (!isNewSegment) {
setToClear = true;
}
break;
}
}
}
// no points, return
if (pointArray.size() == 0) {
return;
}
// fill the path
for (int i = 0; i < pointArray.size(); i++) {
Point p = pointArray.get(i);
if (i == 0) {
if (lastSegmentEndPoint != null) {
if (p.x >= lastSegmentEndPoint.x) {
// if we have the end of the last segment, move to it
// and line to the new point
path.moveTo(lastSegmentEndPoint.x, lastSegmentEndPoint.y);
path.lineTo(p.x, p.y);
} else {
// otherwise just line to the new point
path.moveTo(p.x, p.y);
}
} else {
path.moveTo(p.x, p.y);
}
} else {
path.lineTo(p.x, p.y);
}
}
if (clear || isNewSegment) {
if (clear) {
clear = false;
}
// we need to clear, lock the whole canvas
canvas = holder.lockCanvas();
// draw the graph frame / scales
drawGraphFrame = true;
drawGraphFrame(canvas);
} else {
// just draw the path
RectF bounds = new RectF();
Rect dirty = new Rect();
// calculate path bounds
path.computeBounds(bounds, true);
int extra = 0;
dirty.left = (int) java.lang.Math.floor(bounds.left - extra);
dirty.top = (int) java.lang.Math.floor(bounds.top - extra);
dirty.right = (int) java.lang.Math.round(bounds.right + 0.5);
dirty.bottom = (int) java.lang.Math.round(bounds.bottom + 0.5);
// just lock what is needed to plot the path
canvas = holder.lockCanvas(dirty);
}
// draw the path
canvas.drawPath(path, linePaint);
// unlock the canvas
holder.unlockCanvasAndPost(canvas);
// remember last segment end point
lastSegmentEndPoint = pointArray.get(pointArray.size() - 1);
// set clear flag for next pass
if (setToClear) {
clear = true;
}
}
Draw frame / clear graph code
private void drawGraphFrame(Canvas canvas) {
if (!drawGraphFrame) {
return;
}
if (canvas == null) {
Log.e(TAG, "trying to draw on a null canvas");
return;
}
drawGraphFrame = false;
// clear the graph
canvas.drawColor(Color.BLACK, Mode.CLEAR);
// draw the graph frame
canvas.drawLine(leftMargin, topMargin, leftMargin, mCanvasHeight - bottomMargin, framePaint);
canvas.drawLine(leftMargin, mCanvasHeight - bottomMargin, mCanvasWidth - rightMargin, mCanvasHeight
- bottomMargin, framePaint);
// more drawing
}
Your problem is quite straight forward.. your only locking the new portion of the canvas that the new path covers. So the best thing to do is to make your path and dirty rect's private members of your class. Then at the start of your draw method get the path's current bounds (the old bounds) in your dirty rect. Now call path.rewind(); and start modifying your path. After do a union on the dirty rect with the new bounds. Now your dirty rect covers the old and new rect's. So your clear will remove the old path. This also reduces overhead because you don't want to be allocating 100+ objects per second for rect's and path's. Now since your drawing an oscilloscope then you probably want to adjust the old bounds to only be a portion of the width of the view. The same amount your new portion covers.
Hope that's cleared things up.
My simple answer is just using this function clear_holder() wherever you want to clear the canvas. I copy and paste 3 line for 3 times because it need 3 times clear to leave holder blank.
After clearing holder, you should draw any new thing you want!
This link give me this source code!
private void clear_holder(SurfaceHolder holder){
Canvas c = holder.lockCanvas();
c.drawColor( 0, PorterDuff.Mode.CLEAR );
holder.unlockCanvasAndPost(c);
c = holder.lockCanvas();
c.drawColor( 0, PorterDuff.Mode.CLEAR );
holder.unlockCanvasAndPost(c);
c = holder.lockCanvas();
c.drawColor( 0, PorterDuff.Mode.CLEAR );
holder.unlockCanvasAndPost(c);
}
It looks like you are clearing the canvas so, it's not double buffering problem. I think it's related to your path been reused.
Try adding adding the next line when starting new page.
path.reset();
As is, 100 pink circles (same bitmap) appear scattered randomly over the phone screen (as is supposed to). When I tap one of the circles, that circle should disappear (change to the background color). I think I have a fundamental misunderstanding of Android and View in general.I think I have a couple obvious errors (that are not so obvious to me, but I've been staring at it so long that I figured I needed some help). Currently, the screen shows the random circles but nothing more. Touching the screen does nothing. Any better ideas to make the circles disappear? It recently reorganized all the bitmaps when you touched it, but I did something recently, and it stopped. The bitmap is 30px by 30px.
public class DrawV extends View {
private Bitmap bit_dot;
private int width;
private int height;
public int[] width_array = new int[100];
public int[] height_array = new int[100];
private View dotV = (View)findViewById(R.id.bigdocpic);//bitmap
Random rand = new Random();
public DrawV(Context context) {
super(context);
bit_dot = BitmapFactory.decodeResource(getResources(), R.drawable.dot_catch);
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
width = metrics.widthPixels;
height = metrics.heightPixels;
}
#Override
//draws 100 randomly placed similar bitmaps
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height_dimension;
int width_dimension;
for (int i = 0; i < 100; i++){
height_dimension = rand.nextInt(height) + 1;
width_dimension = rand.nextInt(width) + 1;
canvas.drawBitmap(bit_dot, width_dimension, height_dimension, null);
width_array[i] = width_dimension;//
height_array[i] = height_dimension;//
}
}
#Override
public boolean onTouchEvent(MotionEvent event){
Paint p = new Paint();
p.setColor(Color.WHITE);
Path path = new Path();
Canvas c = new Canvas();
for (int i = 0; i < 100; i++){
if ((event.getX() == width_array[i]) && (event.getY() == height_array[i]))
c.drawCircle(width_array[i], height_array[i], 15, p);
}
invalidate();
return false;//false or true?
}
//set visibility of bitmap to invisible
public boolean onTouch(View v, MotionEvent event) {
dotV.setVisibility(View.INVISIBLE);
invalidate();
return false;//false or true? not understanding
}}
Help?
Your onTouchEvent isn't really doing anything important as-is, and you don't have the concept of a circle object.
onDraw should really be drawing these circles from an array/list created earlier - say a List<MyCircles> or MyCircles[]. On touch, you could iterate through all of your circles until you find one that is closest, remove that circle from the array or list, then invalidate.
The reason nothing is happening at all is even though you're drawing those circles again in onTouchEvent, you're redrawing everything yet again in onDraw (invalidate() calls draw/onDraw).
Ideally, create your list of circles in your initializer, draw them in onDraw, and update them in onTouch (That is, delete). There may be a simpler way to do this but this is, at the very least, a more proper approach.
I was a frequent guest at stackoverflow until I ran into a problem that I really couldn't find anything existing about. So here is my first question:
I am building a camera app in which the user can take several pictures before proceeding to the next step. I want to give the user the possibility to review and delete pictures while stying in the camera stage, so I have written a custom View to show Thumbnails of the already captured images with a delete button. These "Thumbviews" are contained in a LinearLayout that is located on top of the camerapreview-SurfaceView and has a default visibility of "GONE". The user can toggle the visibility with a button.
It all works fine, but I have one problem:
When I take more than about 10 pictures, I get an OutOfMemoryError. The thumbnails are really small and don't take a lot of memory and also I recycle the original Bitmaps and perform a System.gc() after creating the thumbs.
The weird thing is, when I press the button that sets the visibility of the containing LinearLayout to "VISIBLE" and again to "GONE", apparently all the memory gets freed and I can take many more pictures than 10.
I've tried switching the visibility in code but that doesn't work, and also destroying the drawing cache.
There has to be another way to free that memory besides pushing my visibility button 2 times ;-)
Here's the code for the ThumbView:
public class ThumbView extends View {
private Bitmap mBitmap;
private Bitmap mScaledBitmap;
private int mWidth, mHeight, mPosX, mPosY;
static private Bitmap mDeleteBitmap;
private File mPreviewFile;
private File mFinalFile;
private Orientation mOrientation;
private boolean mRed;
public ThumbView(Context context, Bitmap bitmap, File previewFile, File finalFile, Orientation orientation) {
super(context);
mBitmap = bitmap;
mPreviewFile = previewFile;
mFinalFile = finalFile;
mOrientation = orientation;
if(mDeleteBitmap != null)
return;
mDeleteBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.deletebutton);
}
public void deleteFile()
{
if(mPreviewFile != null && mPreviewFile.exists())
{
mPreviewFile.delete();
}
if(mFinalFile != null && mFinalFile.exists())
{
mFinalFile.delete();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(mWidth, mWidth);
if(mBitmap == null)
return;
mHeight = mWidth;
float bitmapRatio = mBitmap.getWidth() / (float) mBitmap.getHeight();
if(bitmapRatio > 1)
{
mScaledBitmap = Bitmap.createScaledBitmap(mBitmap, mWidth,
(int)(mWidth/bitmapRatio), true);
mPosY = (mWidth-mScaledBitmap.getHeight())/2;
}
else
{
mScaledBitmap = Bitmap.createScaledBitmap(mBitmap, (int)(mHeight*bitmapRatio),
mHeight, true);
mPosX = (mHeight-mScaledBitmap.getWidth())/2;
}
Matrix mtx = new Matrix();
mtx.postRotate(-90);
Bitmap b = Bitmap.createBitmap(mScaledBitmap, 0, 0, mScaledBitmap.getWidth(), mScaledBitmap.getHeight(), mtx, true);
mScaledBitmap = b;
b = null;
mBitmap.recycle();
mBitmap = null;
System.gc();
}
public boolean deleteButtonPressed(float x, float y)
{
Rect r = new Rect(mPosY, mPosX, mPosY+mDeleteBitmap.getWidth(),
mPosX+mDeleteBitmap.getHeight());
if(r.contains((int)x, (int)y))
{
return true;
}
return false;
}
public void setRed(boolean red)
{
mRed = red;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mScaledBitmap, mPosY, mPosX, new Paint());
canvas.drawBitmap(mDeleteBitmap, mPosY, mPosX, new Paint());
if(mRed)
canvas.drawColor(0x55FF0000);
}
}
The "why does it not break" answer's easy. When the visibility of a child view (or container) is set to GONE, the parent layout will (generally) skip it and not even bother rendering it. It's not "hidden", it's not there at all.
If your thumbnails are really thumbnails you shouldn't be running out of memory, however, I think you're not downsampling them (I could be wrong). How are you showing them? You should share that piece of code. (New Photo -> Thumbnail Image -> Image View)
I am so stupid. Obviously my onMeasure() won't be called while the View stays GONE and therefore the original bitmap stays in memory. I changed visibility to INVISIBLE and everything works fine now.