Synchronizing UI update with PostDelayed() - android

I'm new to Android development, and running into a synchronization issue. I made a simple matching game where you flip two cards over at a time, and try to find matches. What I want to do is flip the previous two cards over if either:
The user clicks a new card (do it immediately)
After a certain amount of time, if the user does nothing
I tried to safeguard my "flipIfFlipped" routine by marking it synchronized, and checking to be sure that the card has not already been flipped back, but I still get this double-flipping behavior (my force flip must flip it first, then the timer flips it again, or vice versa).
Here's my method on my CardView (ImageView subclass):
public synchronized void flipIfFlipped() {
if (this.flipped) {
this.flip();
}
}
Here's my force flip at the top of my onItemClickListener, if we clicked a card while two are still up:
CardView lastCard = game.getLastCard();
if (null != lastCard){
lastCard.flipIfFlipped();
}
CardView secondLastCard = game.getSecondLastCard();
if(null != secondLastCard) {
secondLastCard.flipIfFlipped();
}
And here's my attempted delayed flip, later, if we just got mismatched cards:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
clickedImage.flipIfFlipped();
lastCard.flipIfFlipped();
}
}, 3000L);
Again, I'm a newbie, so I'm sure I'm probably missing something basic.

Related

Animate android views in order

For a blackjack game I'm making, I need the card to translate to the dealer position from the pile, then to the player position, so I made a general method for it.
private void dealHand() {
animateCard(dealer_iv_1);
animateCard(dealer_iv_2);
animateCard(player_iv_1);
animateCard(player_iv_2);
}
private void animateCard(View v) {
animated_card_iv.animate().x(v.getX()).y(v.getY()).setDuration(1000).withEndAction(new Runnable() {
#Override
public void run() {
animated_card_iv.setX(pile_iv.getX());
animated_card_iv.setY(pile_iv.getY());
}
}).start();
}
As it's written right now it's obviously gonna animate only one card to player_iv_2.
I don't want to wait 1000 millis between animateCard() calls using a Timer or CountDownTimer.
What I would want is for it to be something like this:
private void dealHand() {
animateCard(dealer_iv_1).wait();
animateCard(dealer_iv_2).wait();
animateCard(player_iv_1).wait();
animateCard(player_iv_2);
}
so animateCard() needs to return a type and let me know when it's done. how is this possible?
So far this is what I got:
private void dealHand() {
animateCard(dealer_iv_1, 0);
animateCard(player_iv_1, 1010);
animateCard(player_iv_2, 2010);
}
private void animateCard(View v, long startDelay) {
final ImageView iv = new ImageView(this);
iv.setLayoutParams(new ViewGroup.LayoutParams(104, 149));
iv.setImageResource(R.drawable.back0);
iv.setX(pile_iv.getX());
iv.setY(pile_iv.getY());
((RelativeLayout)findViewById(R.id.pile_layout)).addView(iv);
iv.animate().x(v.getX()).y(v.getY()).setDuration(1000).setStartDelay(startDelay).withEndAction(new Runnable() {
#Override
public void run() {
((RelativeLayout)findViewById(R.id.pile_layout)).removeView(iv);
}
}).start();
}
As you can see I'm now creating 3 ImageViews every time dealHand() is called, and removing each of them after they finish animate, I just gotta ask if I'm doing it properly since I tried calling dealHand() about 50 times which produced 150 of ImageViews, I saw it took about 1.5Mb of RAM in the profiler, this 1.5Mb didn't clean up, even when I called System.gc().
First, ditch the animation library and use the ObjectAnimator library. It's newer and 100x more useful.
Then, use an AnimatorSet to play the animations. AnimatorSets can play animations either sequentially or in order. You can just create your animations and either use the AnimatorSet#Builter or the AnimatorSet#playSequentially to play them all in order.
Then, don't worry about the garbage collection and cleanup. Just remove them from View and the garbage collector will clean them up when it's ready. However, you may consider a more efficient strategy by recycling and re-using the ImageViews each time. That way you're not re-building them constantly. Also, it prevents a lot of heavy, unnecessary garbage collecting. This would only need to be considered if the animations happen frequently.

LayoutManager is not updated even after called within Handler

My situation is the following: I have a RecyclerView in which I want to insert data.
I add data one by one until the RecyclerView is full. The data comes from a web service.
This is the code I use:
#Override
public void receive(Response response) {
_adapter.add(response.getData());
new Handler().post(new Runnable() {
#Override
public void run() {
fetchIfNotFull();
}
});
}
private void fetchIfNotFull() {
if (_layoutManager.findLastVisibleItemPosition() == _layoutManager.getItemCount() - 1)
fetchData(); // this will call receive(Response) when it's done
}
The problem is that, when I run the application, the RecyclerView is not filled, like I expect (but sometimes it does!).
I found out that _layoutManager.findLastVisibleItemPosition() does not always return the correct value (the one I expect at least), whereas _layoutManager.getItemCount() does, so no more data are fetched...
I thought that wrapping the call inside the Handler would help, so it would be called after the next layout update, but it didn't do the trick.
And here is the strange thing: If I call handler.postDelayed() with 1000 milliseconds, it works fine! (I didn't try other values), because the layout was updated after that time. But I don't like this solution (hack). Is there a way to make sure that the LayoutManager has been updated?
After the line
_adapter.add(response.getData());
add this one
_adapter.notifyItemChanged(_adapter.getItemCount() - 1);

Deleting item from ListView from service caused a lot of bugs

I have ListView of Persons, ArrayAdapter to display images of this persons. There is two buttons "like" and "dislike" under photos of each person. If I press like or dislike button, this item deleted with animation from list:
protected void removeListItem(View rowView, final int positon) {
Animation out = AnimationUtils.makeOutAnimation(getActivity(), true);
rowView.startAnimation(out);
rowView.setVisibility(View.INVISIBLE);
Handler handle = new Handler();
handle.postDelayed(new Runnable() {
public void run() {
persons.remove(positon);
mMyAnimListAdapter.notifyDataSetChanged();
}
}, 500);
}
Also there is some API that generates list of person that we can get. This API makes changes to the list (status changes to one of the next value: "none", "removed", "liked, "disliked").
If status changed to "removed" I need to remove this person from list and update ListView. I do this so:
if (changedPerson.getStatus().equals("removed")) {
persons.remove(index);
mMyAnimListAdapter.notifyDataSetChanged();
notifyRemovedStatus(idOfChangedPerson);
}
But when I do this, a lot of bugs occured:
List updates only after tap.
The fragment isn't closed if last item removed from list.
ArrayIdexOfBound Exception some times occured if we press on like/dislike button.
Can you help me, why is this hepenned and how to fix it?
The solution was easy, here code without bugs:
if (changedPerson.getStatus().equals("removed")) {
persons.remove(index);
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
mMyAnimListAdapter.notifyDataSetChanged();
if (persons.size() == 0) {
getActivity().onBackPressed();
}
}});
notifyRemovedStatus(idOfChangedPerson);
}
I violated this rule "Do not access the Android UI toolkit from outside the UI thread".
This video explains working conceop:
https://www.udacity.com/course/viewer#!/c-ud853/l-1469948762/e-1530568562/m-1530568563
And this documentation was useful: http://developer.android.com/guide/components/processes-and-threads.html

Android: Progress bar not showing intermediate states

There are other similar SO questions but none really helped.
I just want to implement a progress bar, that on button click, starts from 0, moves gradually and stops at a point (which is read from sharedPreferences).
However things are working but that progress bar is not gradually updating , instead it appears straight at the end point.
A simplified code I'm pasting here:
public class MainActivity extends ActionBarActivity {
int primaryProgress = 0;
int finalPrimaryProgress = 60;
ProgressBar progressBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
progressBar.setMax(100);
progressBar.setProgress(0);
}
public void click(View view) {
while (primaryProgress < finalPrimaryProgress)
{
progressBar.setProgress(primaryProgress+=1);
Thread.sleep(100); //This is inside Try-catch in actual code.
}
}
}
I know the problem is with Threads, but I am not too friendly with threads so unable to understand the problem.
I'm assuming your "click" method is happening on the UI thread (otherwise, you'd probably get an exception when you try to alter the view on a background thread).
Think about what you're doing here - the moment you click a button, you begin a loop. On each iteration of the loop, you change the value of the progress bar, and then make the (main) thread sleep for 100ms, then repeat.
Problem is, you're never letting the runtime react to what change you made. Once you change a value of a view (for instance, change the text of a TextView), the Android runtime needs to have time to react to your change, adjusting the view's size, position and then redraw. Your code basically blocks anything from happening before you change the value again, because you're telling the main thread to sleep, and then you immediately change the value again.
The expected result here would be seeing the progress bar drawn in its final state once you stopped changing its value without letting the runtime actually do anything with it.
As an alternative to your current click method, try doing something with postDelayed. This will allow the runtime to perform a full measure-layout-draw cycle between every iteration:
private void increaseProgressBar()
{
if (progressBar.getProgress() < progressBar.getMax())
progressBar.postDelayed(new Runnable() {
#Override
public void run() {
progressBar.setProgress(progressBar.getProgress() + 1); // increase the value of the progress bar.
increaseProgressBar(); // call the function again, but after a delay.
}
}, 100);
}
public void click(View v)
{
increaseProgressBar();
}
Note: Keep in mind that currently, clicking the button multiple times will cause erratic behaviour, since your basically adding additional runnables to this sequence, causing the progress bar to grow faster. You could defend against such a case fairly easily with a simple flag.

Android activity life cycle - restarting (destroying) app does not remove reference to (custom) listener?

I have an application using a GlSurfaceView and renderer. I have it set so that when the user exits the application via the back button I call myActivity.finish();
This is fine and I can see the activity getting calls to onStop() and onDestroy();
The app works fine the first time run however when I subsequently run I have had a problem with my motionEvents.
I handle motion events by queuing them into a pool and having the renderer access the pool at the right time like so:
try
{
//Get the history first
int hist = event.getHistorySize();
if (hist > 0)
{
//Oldest is first in the list. (I think).
for (int i=0;i <hist; i++)
{
InputObject input = inputObjectPool.take();
input.useEventHistory(event, i);
defRenderer.feedInput(input);
}
}
//The current one still needs to be added
InputObject input = inputObjectPool.take();
input.useMotionEvent(event);
defRenderer.feedInput(input);
}
And in the renderer:
synchronized (inputQueueMutex)
{
ArrayBlockingQueue<InputObject> inputQueue = this.inputQueue;
while (!inputQueue.isEmpty()){try
{
InputObject input = inputQueue.take();
if (input.eventType == InputObject.EVENT_TYPE_TOUCH)
{
screenManager.processMotionEvent(input);
}
else if (input.eventType == InputObject.EVENT_TYPE_KEY)
{
screenManager.processKeyPress(input);
}
input.returnToPool();
}
catch (InterruptedException ie)
{
DLog.defError("Interrupted blocking on input queue.", ie);
}
}
}
As you can see in the above code I hand these motion events to the ScreenManager which basically is my way of having several "scenes" which I render out. This works fine the first time I run the application and the screen interprets my motion touches into movement of a simple square at the moment.
However the second time I run the application the square is drawn to the screen fine however the motion events do nothing.
I have followed the motion events and although they are given to the "new" renderer it seems to be giving the motion events to an old screen. Or rather to an old object on the screen. This is confusing as in my code in the onCreate() method I do this:
//Set up the renderer and give it to the SurfaceView
defRenderer = new DefRenderer();
defView = new DefView(this);
defView.setRenderer(defRenderer);
//Set out content to the surface view.
setContentView(defView);
//Set up the input queue
createInputObjectPool();
OnCreate is called both the first time and the second time my app is run (and the app was destroyed!) the screens are made new in defRenderer and are given to a new defView.
I am very confused how data could remain in the defRenderer to receive the motionEvents as the app is completely remade.
Is there something obvious going on that I am missing here? I would have thought that when onDestroy is called the app would be completely dereferenced and so no trace of it would remain. Is this not true? Does somehow when I call new Renderer(); is it referencing an old one?
I am at a loss as to what is going on really. Especially as this app is a basic copy of another I have written which works completely fine!
EDIT:
After a small amount of experimentation I have discovered that the motion events are actually going to an old ScrollPanel (an object I made..) which is registered as a listener (and by listener I mean my own implementation ..) for MotionEvents. I have made my own event system for these like so:
public interface TouchSource
public static final int TYPE_TOUCHDOWN = 0;
public static final int TYPE_TOUCHDRAG = 1;
public static final int TYPE_TOUCHCLICK = 2;
public Vector<TouchListener> listeners = new Vector<TouchListener>();
public void addTouchListener(TouchListener listener);
public void removeTouchListener(TouchListener listener);
public void touchOccured(int type, int xPos, int yPos);
}
And the listener interface:
public interface TouchListener
public boolean touchDownOccured(int xPos, int yPos);
public boolean touchDragOccured(int xPos, int yPos);
public boolean touchClickOccured(int xPos, int yPos);
So the Screen implements touchSource and so has a list of the listeners. Now despite being REMADE by Screen currentScreen = new Screen(); called in the OnCreate(); of the manager this list of listeners is still populated with the old ScrollPanel?
How is this? I'm clearly missing something obvious. Like somehow the list of listeners is static for some reason and not getting dereferenced despite the app being completely remade?
I suspect the issue you're facing might have something to do with the fact that the original motionevents are recycled (returned to their pool) by the framework after the onMotionEvent() returns.
From the way you're using your InputObjects, I think you might be keeping a reference to the original motionevents in there, and not copying the event data.
Quickly try using MotionEvent.obtain(event) whereever you use event now (this makes a copy) and see if this makes the weird behaviour go away. Naturally, if this works you will eventually have to recycle() those copies after you're done with them. Do not call recycle() on the original motionevents though.
Cheers, Aert.

Categories

Resources