In an Activity, I am retrieving data from a server. There are a limited number of TableRows containing further Views pre defined in the layout xml file, all set to android:visibility="gone". Further there's an array in the java class with all IDs of these TableRows.
For each entering "data entry", I fill such a TableRow, set it visible and animate it (the animation is a 'push in' from a randomly choosen vertical side). As an further hint, which is important later in this question, every data packet retrieving from the server is related to a month.
Whenever I get data, I do following process
Fill the still invisible TableRows with the information
For every time step 1 is executed, a value in a boolean array is set to true
call showAnimation()
for every entry that is true in the boolean array, get TableRow at index i in the id array, set an Animation and set visibility to visible
This works fine. Even if the visibility is set before starting the Animation, it doesn't matter, since this takes so few time, that the Animation has already begun in our eyes.
Now comes the problem: the reverse way doesn't work. Let me clarify: I want, that the TableRows are pushed out of the window, and THEN set to invisible resp. gone whenever I change the actual month, and the new entries are pushed then in. The problem is, that as soon the Animation starts, the Views are set to invisible / gone. I tried to solve this with an AnimationListener like explained here in the first answer. This leads to two possible ways:
Define such an AnimationListener for each TableRow and set the visibility in the onAnimationEnd(). I find this is very ugly, since it is not dynamical.
Have access to a kind of index in the AnimationListener and set the same Listener to every TableRow's Animation. Here I don't know how possibly solve this. As I discovered earlier, setting Animations on Views set in a loop (a for loop in my case) results in the Animations beeing started when the loop has finished, and not whenever it is set. So even if I would carry a global index variable in that for loop to be used in the onAnimationEnd(), it obviously would came to nothing.
Do someone knows a better way to implement this? Solving it with a Handler.postDelayed() does not do the trick, since it is possible that the new data has already come from the server, and therefor the new entries are already visible (and thus the new ones are set to invisible).
ok I thought a lot about it and honestly I agree with you that it's not the nicest way, but due to the framework limitations the 1st way is still the best one.
It will be nice if we could from the listener call back do:
public void onAnimationEnd (Animation animation){
animation.getView().setVisibility(View.Visible);
}
// but this doesn't exist.
Just make sure every time you're setting an animation to start to do:
Animation anim = row.getAnimation()
if(anim!=null){
anim.setListener(null); // to avoid the listener to be called
anim.cancel(); // to stop it from running
}
// and then go ahead and setup a new animation with new listener
anim = new MyAnimation();
anim.setListener(new SetToInvisibleListener());
row.startAnimation(anim);
Related
This question came to my mind when re-binding data to views in RecycleView. Before selectively applying UI changes to the views, I usually reset them all to their default states.
Such as
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
/* Reseting to normal/default state */
holder.title.visibility = View.VISIBLE
holder.poster.visibility = View.VISIBLE
/* Applying data */
if (data.poster.url == null) {
holder.poster.visibility = View.GONE
}
}
Here the poster View has potentially its visibility changed to VISIBLE and GONE again within a very short time interval.
Does Android actually invalidate and request drawing for both visibility changes? For example, if before binding, holder.poster view was GONE, then Android would draw it visible and then gone again?
Or does it batch them and only execute the latest state? For example, if we want the app to run at 60fps, it might batch UI operations in 16ms intervals. I can change Visibility hundreds of times, but it will actually draw the very last state within that 16ms batch.
The 2nd point holds true, but for a much more simple reason in your use-case.
The onBindViewHolder method runs on the main thread. Thus no other operation is possible on that thread until it returns.
Every change to a view property might invalidate the view. Layouting and drawing happens once, after onBindViewHolder has returned.
Yes, ViewHolders are recycled. That's the reason for their existence: so that expensive-to-build ViewHolders can be recycled to display different items. Yes, you need to zero out any changes to the contents of the ViewHolder that may have been made while an item was bound to it.
Rendering is also batched. Any change you make to a View property invalidates rendering or layout (as appropriate), and layout and rendering passes occur later. Property changes on views ultimately make calls to RequestLayout(), and/or RequestRender() to schedule layout and/or rendering passes at a later time. There is virtually zero cost for the second and subsequent changes.
I'm not quite sure when "a later time" is, but there's no delay involved. Right away, for all practical purposes, but batched. Something along the lines of process all queued messages, and when there are none, start a layout pass if it's been requested.
Rendering then takes place in a separate pass, after the layout pass has taken place.
Recycler view actual does a LOT of work figuring out where items were displayed, where items will be displayed and animating between the old and new location. This occurs in the layout pass, after you have notified the RecylerView that items in your adapter have changed. But it's done in a batch. You can toggle away on visibility to your hearts content. Only one render pass, and one layout pass takes place. (That's a sight simplification; atypically for an Android view, internally, RecyclerView, actually executes two layout passes, but that's not something you need to know or are supposed to know. It's a hack. I THINK you're being called during the first layout pass).
With respect to toggling bits and pieces in an onBindVieHolder call... rest assured that layout and rendering for the contents of the ViewHolder have been thoroughly and completely invalidated by the time you get the call. You are about to display something else, somewhere else. So go to it!
When you watch carefully, the insert animation in a RecyclerView works in 2 steps :
1) White space expands to make room for the new item
2) The item is animated in
Same thing for removal, reverse order
Examples :
https://github.com/wasabeef/recyclerview-animators
http://www.grokkingandroid.com/first-glance-androids-recyclerview/
The second step can be customized easily with the ItemAnimator class, but I cannot find any information on the first step. It happens to be very ugly with my layout (continuous color on the side).
LayoutAnimationController seems like a possible candidate to manage the first step, but adding a new one does not change anything, and following it with logs in setLayoutAnimationListener stays silent.
I cannot even disable this step, which would allow me to manage the animation manually in onBindViewHolder.
Actually sometimes the first step does not seem to fire, but I cannot explain why. I manage all the operations with notifyItem*, no notifyDataSetChanged.
Wanna implement the filling of my ListView with the effect like that: the first item rotates X and fades in, later the second one and so on.
One way to do it is clear - add items one by one to the adapter (just like in that post), while the animations are handled by LayoutTransition object, which I set in advance.
However, I have a strange feeling, like it'd be somewhat a crutch to add items asycnchoniously just in sake of animation effect. Does anybody know how to do it better?
UPD:
Some details: I want items to be animated only when the underlying data changes, for instance, the server sends new info and the list updates, just like the old departure boards in airports.
Use a LayoutAnimationController....
LayoutAnimationController lac = new LayoutAnimationController(AnimationUtils.loadAnimation(getActivity(), R.anim.table_row_appear), 0.5f); //0.5f == time between appearance of listview items.
listView.setLayoutAnimation(lac);
Whenever you want to run the animation :
listView.startLayoutAnimation();
Finally, I ended up with using the LinearLayout instead of ListView because of the view reusage, that reruns the animation for every view whenever it's showed up again.
But I did it just because I didn't have too many items to show. Another approach, as I guess, is to load animations in adapter, compute delay in accordance with item position and to store the map with the info, wether the view has already been animated in or not.
You can create an animation(transition + fade or any other effect you want) and add the animation to the Layout (view) that you return in "getView"
the animation shall take in consideration the "position" parameter to create the delay when the animation is started.
enjoy
daniel
I wasn't exactly sure how to briefly paraphrase my question in the title, so please forgive me as I never post questions until now. I am new to Android/Java for starters, as the main language I have used so far is C++. My question is that I have a game board layout (similar to checkers/chess). When it is the user’s turn, they are to click the piece they want to move on the board and then the blank location they would like to move it to. How can this be accomplished? Up until this point I have implemented onClickListeners that never rely on another button in the activity to be clicked afterwards and wait for the user to do so.
Brief information on my project currently (unsure if needed):
I am currently using an ImageButtons array (of size 36) and a two dimensional integer array to hold the information of each of the buttons, as they are displayed in a GridLayout in a 6 X 6 fashion. In my MainActivity class I have implemented the OnClickListener and created a switch statement in onClick() for each of the button ids.
I am not sure how much more information is needed on my code for help or if it is completly irrelevant. I tried looking on the internet before choosing to ask finding nothing. It is always possible though I was not correctly phrasing my issue. Thank you to everyone in advance!!! :)
Without any code it is hard to say what you are doing wrong/right. But if you have the images stored in an Array then when they click on one image that can be put into a variable which is used to place on the View that they click on next. If you have a more precise question then please post relevant code and error messages from what you have tried, if any. Hope this helps
after a user click's a piece store that info in a member variable like a current piece and then on second click check if the previous is not null, the current clicked space is empty and start your animation by posting to a runnable implementation. this is the simple logic part. where are you having problem exactly
To optimize your code to change images what you can do is custom animations on the imageview. You can change the position of the image with animation without having to create or store an image. (If you use images bitmap a lot you have a very good chance of running into OutOfMemory exceptions and this will happen on top end phones very easily
How to Move a Button and Change the Button Image
I would put a single Button on every GridView position.
Then, I would use button.setBackground() and button.setVisibility() to display and hide the buttons with various images. In this way, the buttons will appear to move, but you are really just displaying a different button.
I recommend this because it is easier to change the visibility and image properties of a button than it is to actually move the button, although both methods are possible.
You will maintain a 1 dimensional array of images[n] and a 2 dimensional array of buttons[6,6].
As an example, suppose you want to move image[5] from grid position (1,2) to position (3,4):
// Hide the button at (1,2);
button[1,2].setVisibility(Button.INVISIBLE);
// Display the button at (3,4) with image #5.
button[3,4].setBackground(image[5]);
button[3,4].setVisibility(Button.VISIBLE);
Additionally, if your button images are stored in your resources, you could efficiently use button.setBackgroundResource(R.drawable.image-5-id);
The instructions above discuss how to move a Button, but now how to trigger one Button to be moved and then trigger the location into which to move the Button.
To accomplish this, you will have to define two states, such as:
private final static int STATE_PICK_BUTTON = 0;
private final static int STATE_PICK_LOCATION = 1;
private int state;
Initialize state = STATE_PICK_BUTTON;
When the system is in the first state STATE_PICK_BUTTON, all button
presses identified in your onClick() function memorize a grid
position to move from, and in some cases transition the system into
the second state: STATE_PICK_LOCATION.
When the system is in the second state STATE_PICK_LOCATION, all
button presses actually move the button from the memorized grid
position to the grid position of this button press.
Of course, you will have to do all sorts of error checking to make
sure you are allowed to move a button before triggering the state
transition.
Finally, the above suggestion may not work, because it may be impossible to click an invisible button. If this is the case, instead of changing the visibility of Buttons in empty grid locations, leave all the buttons Button.VISIBLE and use a fully transparent Button image for the buttons representing empty grid spaces.
I have a view in android in which I need to sequentially highlight and unhighlight buttons when a particular control is pressed. This is a usability feature -- as each button is highlighted, it is read aloud to the user. I use the AlphaAnimation class to accomplish the highlighting. However, now matter how many invalidate calls or callback threads I put in, only the last animation is actually presented to the user. The others are still called but overridden by the last animation so only one is shown. I have also tried simply setting the opacity/alpha properties of the buttons, but all of these modifications end up being batched and performed all at once. I need these animations to occur in a sequence. I have not yet found a good way to update the main view before control flow is returned to it (i.e. update it multiple times before the method it calls returns). Does anyone have any ideas?
For everyone else who runs into this problem, I finally found a solution.
The documentation:
http://developer.android.com/reference/android/os/AsyncTask.html
http://www.anddev.org/tut_updating_ui_from_a_thread-t11125.html
My solution: create an AsyncTask that takes in an array of buttons. In its doInBackground method, it highlights/dims and then pushes an update of the effected buttons to the onProgressUpdate method during each iteration in the button highlighting scheme. The onProgressUpdate method calls invalidate() on all the buttons passed in. Then for easy access to the highlighting functionality, I created a static highlight(buttons...) method that creates a new instance of the AsyncTask and calls execute on it.