I've created a custom view that draws via onDraw() overridden method some shapes. This view is scrollable so every time user navigates in the Activity, onDraw() method has called and all the canvas is drawn. In the onDraw() method there are some statements making some hard calculations so my intent is to draw, when user scrolls the view, only the part that were invisible and now, for the scrolling, they are visible.
How can I draw only the part that are visible in my custom view?
#Override
protected void onDraw(Canvas sysCanvas)
{
super.onDraw(sysCanvas);
if(!giaDisegnato) //If I've never drawn before, let's draw
{
if(!listaTl.isEmpty())
{
toDisk= Bitmap.createBitmap(w,h,Bitmap.Config.RGB );
canvas = new Canvas(toDisk);
canvas.drawColor(Color.WHITE);
p.setStyle(Paint.Style.FILL_AND_STROKE);
p.setAntiAlias(true);
p.setStrokeWidth(1);
for(TimelineGrafica t : listaTl)
{
if(inseritaLaPrima)
y = ySalvata + this.yAngoloDestroGiu + DISTANZA_FRA_TIMELINE;
p.setColor(t.getColor());
disegnaPunta(canvas,p,t);
disegnaRettangolo(canvas,p,t);
disegnaGrain(canvas,p,t);
disegnaFatti(canvas,p,t);
inseritaLaPrima = true;
}
y = ySalvata;
inseritaLaPrima = false;
sysCanvas.drawBitmap(toDisk,0,0,p);
}
requestLayout();
giaDisegnato = true;
}
else
{
//Here I've already drawn. So I'd like to redrawn the part of the view that now
//is visible.
sysCanvas.drawBitmap(toDisk,0,0,p);
}
}
Due to the language, it is difficult to know precisely what you are doing.
However, you can check the canvas to know whether you should draw or not using quickReject.
Example:
if(canvas.quickReject(boundingRect, EdgeType.BW)) {
return;
}
Related
I'm working in an app that uses the ml kit text recognition library; The app reads text from images and puts a Rect around every word.
Now I want these Rects to change color when the user tap on one of them or swipe above some words.
So, I was able to handle touch events correctly, but what I can't do is changing the color of the touched Rect!
Should I draw new colored Rects above the touched Rect? Or can I color the existing rects (which I don't think I can)?
Classes:
TextGraphic, GraphicOverlay.
//This is where the Rects get drawn
I also tried this solution, so I typed this methods in the TextGraphic class:
public void changeColor(boolean isHighlighted) {
if(isHighlighted) {
rectPaint.setColor(COLOR_HIGHLIGHTED);
rectPaint.setAlpha(400);//set opacity of rect
}else{
rectPaint.setColor(COLOR_DEFAULT);
rectPaint.setAlpha(400);//set opacity of rect
}
postInvalidate();
}
and called it when the user touches the text, but the problem is that all Rects colors get changed, and they do not change at runtime!
A snippet from my ActivityClass, where I used some callback methods to pass things out.
ArrayList<Rect> rects = new ArrayList<>();
#Override
public void onAdd(FirebaseVisionText.Element element, Rect elementRect, String wordText) {
GraphicOverlay.Graphic textGraphic = new TextGraphic(mGraphicOverlay, element);
mTextGraphic = new TextGraphic(mGraphicOverlay, element);
mGraphicOverlay.add(textGraphic);
rects.add(elementRect);
}
A snippet from my ActivityClass where I handle touch events:
#Override
public boolean onDown(MotionEvent event) {
helper.dismissKeyboard();
touchX = Math.round(event.getX());
touchY = Math.round(event.getY());
for(int x=0; x< rects.size();x++) {
if (rects.get(x).contains(touchX, touchY)) {
// line has been clicked
mTextGraphic.changeColor(true);
return true;
}
}
return true;
}
You are changing the color using the mTextGraphic variable. If you look closely in your onAdd() method you will see that you are assigning a new object to mTextGraphic that has nothing to do with the objects drawn to screen because only the objects that you add to GraphicOverlay list using the mGraphicOverlay.add() will get drawn to screen.
So what you need is to call changeColor() not on mTextGraphic but on the respective object that is already in the list inside GraphicOverlay
Since the list inside GraphicOverlay is private you can't manipulate it in the onDown() method. You will need to write a public method that will do the job for you.
Write the following method in GraphicOverlay class
public TextGraphic getGraphicElementAtIndex(int index) {
return (TextGraphic)graphics.get(index)
}
Now use this method inside the if condition of onDown() method like this
if (rects.get(x).contains(touchX, touchY)) {
// line has been clicked
Log.d("PreviewActivity", "changeColor() is called");
mGraphicOverlay.getGraphicElementAtIndex(x).changeColor();
touchedText = texts.get(x);
return true;
}
Hope this helps.
SIDE NOTE: Now even after this if for some reason the ordering of objects inside rects list and graphics list (which is inside GraphicOverlay) change then you will see that when you click a rectangle some other rectangle changes color.
Maybe you should not do it by coding but by ColorStateList
Android Developer: colorstatelist
i'm struggling with this for a while, not sure if because almost 2 years of absence in Android DEV or my stupidity, I've tried everything and just cannot redraw my screen even if invalidate() is happening. Here's some code:
GameActivity.java
onCreate
...
final CanvasActivity mCanvasActivity = new CanvasActivity(this);
setContentView(mCanvasActivity);
mCanvasActivity.setOnTouchListener(new OnSwipeTouchListener(this) {
#Override
public void onSwipeTop() {
tilesArray[playerPositionY][playerPositionX] = 0;
playerPositionY--;
tilesArray[playerPositionY][playerPositionX] = 2;
mCanvasActivity.invalidate();
}
CanvasActivity.java
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
movePlayer(canvas);
Log.e("player", "x " + playerPositionX + " y " + playerPositionY);
}
movePlayer
if (currentBlock == 0) {
canvas.drawRect(posLeft, posTop, posRight, posBottom, paintWall);
} else if (currentBlock == 1) {
canvas.drawRect(posLeft, posTop, posRight, posBottom, paintLabirynth);
} else if (currentBlock == 3) {
canvas.drawRect(posLeft, posTop, posRight, posBottom, paintExit);
} else {
canvas.drawRect(posLeft, posTop, posRight, posBottom, paintCharacter);
}
So basically, when We swipe to the top, position is being changed by movePlayer (decreasing y in 2d array). Then, every rectangle on screen is being redrawn (whole screen has only rectangles which are drawn with different colors according to array line by line, doesn't matter I suppose). My variables are changing properly, so invalidate() is firing onDraw(), however there's no change on the screen. Any help much appreciated.
Looks like I didn't reset paintY to 0 every onDraw call and it was painting properly, but below the screen. Like I said, could be, and was, my stupidity :).
currently I am trying to make an animation where some fish move around. I have successfully add one fish and made it animate using canvas and Bitmap. But currently I am trying to add a background that I made in Photoshop and whenever I add it in as a bitmap and draw it to the canvas no background shows up and the fish starts to lag across the screen. I was wondering if I needed to make a new View class and draw on a different canvas or if I could use the same one? Thank you for the help!
Here is the code in case you guys are interested:
public class Fish extends View {
Bitmap bitmap;
float x, y;
public Fish(Context context) {
super(context);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.fish1);
x = 0;
y = 0;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap, x, y, null);
if (x < canvas.getWidth())
{
x += 7;
}else{
x = 0;
}
invalidate();
}
}
You can draw as many bitmaps as you like. Each will overlay the prior. Thus, draw your background first, then draw your other images. Be sure that in your main images, you use transparent pixels where you want the background to show through.
In your code, don't call Invalidate() - that's what causes Android to call onDraw() and should only be called from somewhere else when some data has changed and needs to be redrawn.
You can do something like this, where theView is the view containing your animation:
In your activity, put this code in onCreate()
myAnimation();
Then
private void myAnimation()
{
int millis = 50; // milliseconds between displaying frames
theView.postDelayed (new Runnable ()
{
#Override public void run()
{
theView.invalidate();
myAnimation(); // you can add a conditional here to stop the animation
}
}, millis);
}
The event onDraw() is emitted every time the View need to be repainted cause some graphic changes inside.
Unfortunately if the View is hidden (invisible) this event is not emitted since, obviously, there is no need to repaind anything. However I would to know if there is some trick to "cheat" the View to emit the onDraw() event and redraw itself exactly like it would really showed inside the screen.
Basically I need to capture screenshot status of a View component in all its changes but whitout show it (running it in background).
I guess it would be very hard to get such result but, just in case, I'll try to ask.
Thank you
I believe a View's visibility is checked by its parent and not the View itself. You can pass in a Canvas backed by a Bitmap straight in to View#draw(Canvas canvas) and it will draw itself on to the Bitmap. However, the based on the source code of View#setVisibility(), the View's background will still be invisible.
public void setVisibility(int visibility) {
setFlags(visibility, VISIBILITY_MASK);
if (mBGDrawable != null) mBGDrawable.setVisible(visibility == VISIBLE, false);
}
Everything else should appear in the View as is (unless it's children are also set to invisible of course).
EDIT:
Converting a view to Bitmap without displaying it in Android?
There are examples there on how to do that.
public static Bitmap getBitmapFromView(View view) {
Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(returnedBitmap);
view.draw(canvas);
return returnedBitmap;
}
EDIT 2:
Since setVisibility() is not part of the View, you could override it and simply not set the View to be invisible. Something like:
boolean isInvisible = false;
#Override
public void setVisibility(int visibility) {
if (visibility == View.INVISIBLE) {
invisible = true;
} else {
invisible = false;
super.setVisibility();
}
}
#Override
public void onDraw(Canvas canvas) {
// change state code
if (!invisible) {
// draw code
super.onDraw(canvas);
}
}
#Override
public void draw(Canvas canvas) {
if (!invisible) {
// draw code
super.draw(canvas);
}
}
I have no idea what side-effects this would cause so be extremely careful and weary. Perhaps someone else would have a better solution. Another solution is you can simply call onDraw() on a different canvas whenever you want to draw it. This would require you to create a super class that is the parent layout View of the View you want to draw. Then in the parent's onDraw() method, call the child's onDraw() method separately if it's visibility is set to INVISIBLE.
If someone is interested I managed to solve this problem by override the invalidate() method. Instead of onDraw() that is called by the system only if the view is currently visible the invalidate() function is called "internally" and can be used to check if the view need to be reapinted in the same way.
I am kinda new to android and I am creating a small app where I can draw rectangle and circle on custom view.
What I am trying to do is when Circle button is pressed I want to draw circle and when Rectangle button is pressed I want to draw a rectangle.
But the problem, I am having is when I press circle button it draws the circle correctly, but when I click on the rectangle button and tries to draw it, it hides the previous drawn circle and draws a rectangle. If again I select the circle, and draw it, it will hide the rectangle and draws a circle.
MainActivity.java: when button is pressed, buttonPressed method is called:
private DrawingView drawView;
public void buttonPressed(View v)
{
String shape = v.getTag().toString();
if(shape.equals("circle"))
{
Log.e("button pressed", "circle");
drawView.setValue("circle");
}
else if(shape.equals("rect"))
{
Log.e("button pressed", "rect");
drawView.setValue("rect");
}
}
and in my DrawingView class's onDraw method I am doing:
#Override
protected void onDraw(Canvas canvas)
{
if(testValue.equals("rect"))
{
// draw rectangle
}
else if(testValue.equals("circle"))
{
// draw circle
}
}
// setter method to set value
public void setValue(String val)
{
testValue = val;
}
Any suggestions on how to handle this situation?
Android Views will automatically clear their canvas before onDraw() is called, so you'll have to do one of two things:
Keep a list of items to draw, add to the list when a button is pressed, and iterate over it in onDraw(). This is simple to implement, but might slow down the draw process if your list gets long. This happens on the UI thread, so be careful.
Make your own Canvas from a private Bitmap matching the size of your View, and draw on it when a button is pressed. In onDraw(), use drawBitmap() to copy your buffer onto the View.