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 :).
Related
When i call setVisibility on view's child while the (parent) view is animated with ViewCompat.postOnAnimation things get broken. (setVisibility doesn't work + some other things get broken).
Question - is there any method of animation or workaround which allows to call setVisibility on child while the parent is animated?
This is very important request and i think not so unusual, because for example http request is returned in random time, and the view can be animated anytime during that.
Code request edit:
Regarding code, it is bit complicated. I will first explain. It is animation in the custom CoordinatorLayout Behavior, clone of the standard BottomSheetBehavior (sliding of sheet from bottom to up).
Animation is launched by calling this:
ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState));
SettleRunnable is this:
private class SettleRunnable implements Runnable {
private final View mView;
#State
private final int mTargetState;
SettleRunnable(View view, #State int targetState) {
mView = view;
mTargetState = targetState;
}
#Override
public void run() {
if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) {
ViewCompat.postOnAnimation(mView, this);
} else {
setStateInternal(mTargetState);
}
}
}
So as you can see, all the animation movement is done by mViewDragHelper.continueSettling. Drag helper is standard class ViewDragHelper.
ViewDragHelper.continueSettling looks like this
public boolean continueSettling(boolean deferCallbacks) {
if (mDragState == STATE_SETTLING) {
boolean keepGoing = mScroller.computeScrollOffset();
final int x = mScroller.getCurrX();
final int y = mScroller.getCurrY();
final int dx = x - mCapturedView.getLeft();
final int dy = y - mCapturedView.getTop();
if (dx != 0) {
ViewCompat.offsetLeftAndRight(mCapturedView, dx);
}
if (dy != 0) {
ViewCompat.offsetTopAndBottom(mCapturedView, dy);
}
if (dx != 0 || dy != 0) {
mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
}
if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
// Close enough. The interpolator/scroller might think we're still moving
// but the user sure doesn't.
mScroller.abortAnimation();
keepGoing = false;
}
if (!keepGoing) {
if (deferCallbacks) {
mParentView.post(mSetIdleRunnable);
} else {
setDragState(STATE_IDLE);
}
}
}
return mDragState == STATE_SETTLING;
}
It simply animates the sheet up or down to desired position according the chosen target state.
Pseudo code of problem is:
launchAnimation(); // it takes eg 300 ms
changeVisibilityOfAnimatedViewChildren(); // this is problem
I can wait until the animation finishes, but as i said, in case of http request it is bit problem, i would like to ideally refresh the data right away without waiting.
Animated element is CoordinatorLayout. Affected child by setVisibility is one or more its children.
Judging by this link, android seems to have generally problem with animations and setVisibility.
Possible solutions i am thinking of now:
Maybe if i would change the visibility with another parallel postOnAnimation() task (?)
Or because it are basically just step by step subsequent calls of moving function mViewDragHelper.continueSettling() why don't do it without postOnAnimation()? I could run the task also without it. But i guess that postOnAnimation chooses some correct delay of animation step for concrete device + probably some other things.
You can add AnimatorListenerAdapter to your parent animation, and override onAnimationEnd() method. In this method you can call the child animation. However, I would rather change alpha of view than visibility. You can achieve more smoothly effect in this case.
For example, consider this code:
parentAnimationInstance.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
childView.animate()
.alpha(1.f)
.setDuration(200)
.start();
}
});
Using setTranslationX, I'm trying to animate a view as I swipe it across the screen. Then after it passes a threshold-X, I assign the view a new RelativeLayout.RIGHT_OF.
I want it to stop animating (whether or not I continue swiping) at that point and basically lock to that new anchor.
This is where the problem is: suddenly the view jumps X position to the right of its new anchor.
I've tried, when it's >= threshold, to set setTranslationX(0), but then I see the view twitch/flash twice, once to its original 0, then to the new 0.
I would love to get rid of that double twitch/flash, but don't know how at this point.
#Override
public void onChildDraw(Canvas c ... float dX) {
threshold = anchorView.getRight();
if (animate) {
if (dX >= 0) {
translationX = Math.min(dX, threshold);
if (dX >= threshold) {
translationX = 0; // (A) if I do this, then mainView flashs twice: original 0, then new 0
setToRightOf(mainView, anchorView);
mainView.invalidate(); // has no effect
}
} else {
translationX = 0;
}
// if I don't do (A), then mainView will suddenly jump to 2*threshold
mainView.setTranslationX(translationX);
return;
}
super.onChildDraw(c ... dX);
}
Okay, instead of assigning RelativeLayout.RIGHT_OF during onDraw to set the threshold boundary, I took it out and assigned it when my touch left the screen.
But to insure I wouldn't swipe back behind that threshold while swiping, I had to add another case to check translationX and instead of previously trying to rely on the RelativeLayout anchor.
Now, I'm using setTag() and getTag() to help confirm the threshold during the swipe:
if (dX >= 0) {
if ((Object) past != tag)
translationX = Math.min(dX, threshold);
else
translationX = threshold;
if (dX >= threshold) {
if ((Object) past != tag) {
anchorView.setTag(past);
}
}
} else {
...
}
Plus a couple other places to make sure I reset anchorView's tag and the translationX when needed, then it's all good.
It works for now!
(doesn't directly solve the double flash/twitch issue, but a different approach to the same goal)
(any other recommendations besides using setTag()?)
P.S. In my earlier attempts, instead of invalidate(), I later tried mainView.requestLayout() with no success either, thinking requestLayout() also factors in position.
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);
}
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;
}
I am developing an android app and I should draw a line from the Point A to the Point B but I would like to show this line be drawing "progressively" from one point to other. How can I do it?
Thanks a lot!
You can use a Thread to progressively increase the size of your line, by increasing its ending x position, as an example, and then call View.postInvalidate() from your thread.
This way, if x is the ending of your line and view the View that draws a line ending at x you would have this thread:
class LineThread extends Thread {
public void run() {
while (x < some_limit) {
x += some_value;
view.postInvalidate();
try {
sleep(some_time);
}
catch(InterruptedException e) {
log.e(getClass.getName(), "sleep() was interrupted", e);
}
}
}
}
and in your view:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawLine(left, top, right + x, bottom, paint);
}
Making LineThread a subclass of your View class would make it easy as they could share x.
You could have a look at linear interpolation, but I'm not sure if it'll be too time consuming just for this - should be fine if you're only doing it for a few lines at a time!
Since you have the start_point and your end_point, you can lerp to find the current_point for each timeframe and then use drawLine between start_point and current_point.