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));
}
Related
I'm still searching for the right way to draw on canvas multiple times.
Wished result: ImageView set to visible, show first text, then second instead of first, finally third instead of second. Each text is visible for half a second
Idea: paint canvas with setColor(Color.WHITE) after each text, use synchronized block for setting backgroundcolor and text, use invalidate() with custom draw() method
I tryed different ways, this is the current version of my code:
initializeStimulus() - called in onCreate() of my Service
private void initializeStimulus(String stimulus) {
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
stimulusOverlay = new CustomImageView(this);
stimulusOverlay.setBackgroundColor(getResources().getColor(R.color.background_stimulus));
stimulusOverlay.setVisibility(View.VISIBLE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
);
// Position of stimuli in relation to screen size / 2
Point displaySize = new Point();
windowManager.getDefaultDisplay().getRealSize(displaySize);
int width = displaySize.x;
int height = displaySize.y;
params.x = 0;
params.y = 0;
windowManager.addView(stimulusOverlay, params);
imageBitmap = Bitmap.createBitmap((width/3)*2, height/5, Bitmap.Config.ARGB_8888);
p = new Paint();
Canvas canvas = new Canvas(imageBitmap);
p.setColor(Color.BLACK);
Typeface non_proportional_font = Typeface.createFromAsset(getAssets(), "fonts/luximr.ttf");
p.setTypeface(non_proportional_font);
float scale = getResources().getDisplayMetrics().density;
p.setTextSize(40 * scale);
fillTextPathArray();
stimulusOverlay.setImageBitmap(imageBitmap);
stimulusOverlay.draw(canvas);
}
private void fillTextPathArray(){
mTextPaths = new ArrayList<Path>(VOC_COUNT);
String[] text = new String[VOC_COUNT];
String stimulus = getStimulusFromPref(positionStimulus);
int stimulusLength = stimulus.length();
String masking = String.format("%0" + stimulusLength + "d", 0).replace('0', 'X');
text[0] = masking;
text[1] = stimulus;
text[2] = masking;
for (int i = 0; i < text.length; i++) {
Path path = new Path();
p.getTextPath(text[i], 0, masking.length(), imageBitmap.getWidth()/4, (imageBitmap.getHeight()/3)*2, path);
path.close(); // not required on 2.2/2.3 devices
mTextPaths.add(path);
}
}
This function made an ArrayList of paths, which I need to be displayed in the right order. So at the end, the user will be able to see my canvas changing from first text to second text to third text.
I started with tips like "redraw entire view" or "draw box with backgroundcolor over old text" (Android Canvas Drawing Text and Change Text afterwards), because canvas drawing couldn't be revesed. Didn't work out for me.
The big problem remains: the code runs through and everything will be shown in one canvas, one above the other. AND: Time delays like while loop or Thread.sleep(500) didn't have any impact on the canvas. The result was the same: The code was running through - with the given time delay - and at the end everything was painted, not step by step (first, second, third).
I even tryed to draw each text in separate methods.
So I tryed to implement each given new Canvas, new Paint and even new View - nothing changed. At the end everything was drawn instead of step by step.
Currently I have my custom ImageView (CustomImageView), where I'm working with the invalidate() method while using a synchronized block.
private class CustomImageView extends AppCompatImageView {
private boolean stimulusVisible = false;
private int countStimuli = 0;
public CustomImageView(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
if (!stimulusVisible) {
Object lock = new Object();
synchronized (lock) {
canvas.drawPath(mTextPaths.get(countStimuli), p);
stimulusVisible = true;
countStimuli++;
}
} else {
long startTime = System.currentTimeMillis();
while((System.currentTimeMillis()-startTime) < 500){}
canvas.drawColor(Color.CYAN);
stimulusVisible = false;
}
invalidate();
}
}
The while loop should stop everything for half a sec.
I really hope for your suppoert.
Is there a better way to implement this?
Am I missing something? I though invalidate() would redraw the canvas but it didn't have an impact. I'm trying for 2 days now, please help.
I want to control drawing on Surface View from MainActivity. (user) Github link of the project is given at the end of the post.
MainActivity calls draw(Drawing) method on SurfaceView instance.
I have following code, the surface view only draws when I enabled breakpoint debugging.
I already visited these links, no solution found:
Android SurfaceView empty
Drawing using the Surfaceview creates blank screen
Android drawing on surfaceview and canvas
Tutorials I folowed:
Drawing Triangles, Rhombuses and Other Custom Shapes on an Android Canvas
Android Coding - Drawing on SurfaceView
I thought it was a matter of view creation being in the process so I added checks for creation:
public void draw(Drawing drawing) {
this.drawing = drawing;
if (surfaceHolder.isCreating()) {
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new Thread(LushSurfaceView.this);
thread.start();
}
});
} else {
thread = new Thread(this);
thread.start();
}
}
Here is thread:
#Override
public void run() {
if (drawing != null) {
switch (drawing) {
case RECT:
drawRectShape();
break;
}
}
}
Also I added delays deliberately to wait for the surface view initialization.
private void drawRectShape() {
if (surfaceHolder.getSurface().isValid()) {
Canvas canvas = surfaceHolder.lockCanvas();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (canvas != null && !surfaceHolder.isCreating()) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(30);
Random random = new Random();
int w = canvas.getWidth();
int h = canvas.getHeight();
int left = Math.round(w/2);
int top = Math.round(h/2);
int right = 200;
int bottom =200;
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
paint.setColor(0xff000000 + (r << 16) + (g << 8) + b);
canvas.drawRect(new Rect(left, top, right, bottom), paint);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
Code works when: I enable breakpoint debugging.
Simple debugging does not show anything drawn.
I recreated this scenario several times.
I am using Nexus 6 (OS 7.1.1) as testing device. (Not Emulator).
Project source at github.
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();
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();
}
In my Android app, I am trying to show letters one by one with a short delay between each, while also playing a sound for each letter. I have everything working, and the sounds play with the correct delay, but the text always prints to the screen far too fast. The canvas seems to be updated even when i am not specifically invalidating the view.
Here is what I have so far - I also tried a variant of this based on the "snake" example and had the same results... any help would be appreciated!
public class SpellingView extends View {
private static final String WORD = "TRUCK";
int width;
int height;
String textToPrint;
float textspace;
int j=0;
private final Path arc;
private final Paint tPaint;
//constructor for SpellingView
public SpellingView(Context context) {
super(context);
arc = new Path();
tPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
displayLetterLoop();
}
public void displayLetterLoop(){
for (int i = 0; i < WORD.length(); i++){
final Runnable mUpdateUITimerTask = new Runnable() {
public void run() {
Spelling.mp.start();
}
};
final Handler mHandler = new Handler();
mHandler.postDelayed(mUpdateUITimerTask, i*1500);
}
}
#Override
protected void onDraw(Canvas canvas) {
int k;
// Drawing commands go here
width = canvas.getWidth();
height = canvas.getHeight();
arc.addArc(new RectF((width*.15f), (height*.15f), (width*.85f), (height*.4f)), 180,180);
tPaint.setStyle(Paint.Style.FILL_AND_STROKE);
tPaint.setColor(Color.RED);
tPaint.setTextSize(height * 0.1f);
tPaint.setTextAlign(Paint.Align.LEFT);
setBackgroundColor(Color.BLACK);
for (k = 0; k < j; k++){
char c = WORD.charAt(k);
String cs = Character.toString(c);
textToPrint+= cs;
textspace =(float) (k*(width/WORD.length())*.9);
canvas.drawTextOnPath(cs, arc, textspace , 0, tPaint);
}
if(j<WORD.length()){
j++;
}
}
}
Custom view will invalidate itself when is a part of a layout which for some reason redraw itself. Therefore you could envelop your code in onDraw() with a condition and a flag so that it draws your stuff only when the timer sets the flag and calls invalidate. After one letter is drawn then the flag shoud be set on false like:
if (drawLetter){
drawLetter = false;
/code...
}
However this also may need to be a sychronized block.
OnDraw should happen 60 times a second and not only when you are invalidating.
So maybe you need to update some class variables (when you are invalidating) and use those for your draw logic # OnDraw.