Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[api] Add support for wraparound animation #88

Merged
merged 4 commits into from
Apr 19, 2018
Merged

[api] Add support for wraparound animation #88

merged 4 commits into from
Apr 19, 2018

Conversation

jinatonic
Copy link
Contributor

closes #72

@jinatonic jinatonic self-assigned this Feb 18, 2018
@@ -115,7 +115,7 @@ float getMinimumRequiredWidth() {
* A helper method for populating {@link #startIndex} and {@link #endIndex} given the
* current and target characters for the animation.
*/
private void setCharacterIndices() {
private void setCharacterIndices(boolean wraparound) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've just taken a quick glance at this, will do a full review tomorrow. just wanna ask - does the wraparound always force an upward animation? if i'm going from 9 to 8, i'm assuming we still animate downwards?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, it will always wrap-around and animate upwards.

numFromStart,
numToEnd
);
currentCharacterList = modifiedWraparoundCharList;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if some of the internals here would be made a lot easier by using a circular buffer/ring type instead of a list? i'm not sure if it complicates other places, but we could compute which direction would be a shorter distance to travel and use that to determine whether we should go upwards or downwards. i think it would avoid needing to do these arraycopys, and possibly some other shenanigans.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i started going down that route, but it ended up being quite a bit of change for a somewhat uncommon use-case (a lot of the internal drawing logic had to be re-worked). I decided that it was easier to just use the exact same drawing logic and just properly set up the constraints for it.

i do think some of this logic can be simplified using better data structure, just too lazy to do it now lol..

*
* @param characterListWraparound whether or not to enable character list wraparound.
*/
public void setCharacterListWraparound(boolean characterListWraparound) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the reason i asked about the behavior, is it seems to me like this should be the default behavior (from a ui perspective), so it seems odd to expose a flag for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are saying that wrap-around should be the default behavior? im not sure if having the ticker always animate in one direction is good haha. i feel like wraparound is a rare/specific use-case, basically when you are trying to build a timer or something.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i misunderstood a bit, lemme back up.

i think ideal behavior would be animating up when doing so would cause a wraparound, but the total distance via wraparound is lower than the total distance via not wrapping around. for cases where we go from 9 -> 0, that's a distance of 1 via wraparound, but 8 via not wrapping around. we should just always do the shortest.

having a force flag seems odd, i'm not sure that folks would want an 'always up behavior'. i guess some apps might, but it seems pretty limited.

so, i think we should work towards doing the shortest distance (wraparound or not), and not put it behind a flag (it should just be the behavior of the lib).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see, yeah i like that.

* it will know that it has to go from 'd' to 'c' to 'b', and these are the characters
* that show up during the animation scroll.
*
* @author Jin Cao, Robinhood
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:smug:

}
}

public class AnimationCharacterIndices {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could just be a Pair<Integer, Integer>

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is assuming we could also keep this class internal, which would be great

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i exposed this class to allow for overriding for custom behavior, but you are right using a pair is easier until other needs arise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually im reverting back to use a custom class because Pair is an android class and it breaks my unit tests

}

private int getIndexOfChar(char c) {
if (c == TickerUtils.EMPTY_CHAR) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not loving the special case handling of the empty_char. is there any way to get this for free? like enforcing the empty_char is always at 0? or why do we even need it at 0? can we eliminate that restriction?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looping back here - maybe user passes in "abcdef...", and when we wrap to the TickerCharacterList, we add the empty_char at index 0, then don't have to special case here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so i went back and forth on this one for a while. the reason why the empty char is needed in the saved character list is that we use the character list for animation purposes (so we don't have to construct a new char array for each animation). I originally played around with saving the empty char index (which is 0) into the map. however, this actually made wrap-around animation a little more complicated than i would like.

e.g., given the characters "abcd", the persisted character list for animation is "_abcdabcd". the reason why we save two copies is so that we can easily animate from 'd' -> 'a' this way. Now, if we persist the actual indices, _ would be 0, 'a' would be 1, 'b' would be 2, and so on. However, computing the target distance wraparound would then become something like numOriginalCharacters - startIndex - 1 + endIndex, and I didn't like the arbitrary 1 there.

i liked this way slightly more in the end because from the perspective of character indices there is no concept of added characters, and let getIndexOfChar deal with manipulating the saved indices.

eh, i can go either way at this point. im gonna see if i can think of a better way to handle empty chars in general. the main pain point as pointed out earlier is that i want to have it included in the main char array for animation purposes which i dont think we can avoid.

public TickerCharacterList(String characterList) {
if (characterList.contains(Character.toString(TickerUtils.EMPTY_CHAR))) {
throw new IllegalArgumentException(
"You don't need to include TickerUtils.EMPTY_CHAR in the character list.");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

message is a bit misleading - "You cannot include TickerUtils.EMPTY_CHAR in the character list"

// The saved character list will always be of the format: EMPTY, list, list
private final char[] characterList;
// A minor optimization so that we can cache the indices of each character.
private final Map<Character, Integer> characterIndicesMap;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this still work now that the same char maps to multiple indices?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep see explanation above. it will use the original index in the list.

*/
public class TickerCharacterList {
private final int numOriginalCharacters;
// The saved character list will always be of the format: EMPTY, list, list
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clever use of a second list to get the wraparound. i haven't gotten that far yet, but curious how we handle wraparound if you're at the end of the second list? seems like whatever the solution is there should be applicable to the first list

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah the second list is entirely for wraparound, the persisted indices for the characters always refer to the indices in the first list so shouldn't have any problems there.

void setCharacterLists(String... characterLists) {
this.characterLists = new ArrayList<>(characterLists.length);
this.characterIndicesMaps = new ArrayList<>(characterLists.length);
void setCharacterLists(TickerCharacterList... characterLists) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this used to take in a String... and each TickerCharacterList takes in a String, can we hide TickerCharacterList from the api, and just wrap them internally here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i dont see a huge use-case for exposing this for allowing extensibility so ill change the public API to use string

final int nonWrapDistance = startIndex - endIndex;
final int wrapDistance = numOriginalCharacters - startIndex + endIndex;
if (wrapDistance < nonWrapDistance) {
endIndex = numOriginalCharacters + endIndex;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i'm still not 100% sure about the second list thing -

say we wraparound from 9 -> 3, then we go from 3 -> 7, 7 -> 9

at this point, are we displaying the 9 from the second list? if so, i think if we try to go from 9 -> 3 again, won't it always go down instead of wrapping around?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see explanation above, the indices for animation will always use the first list, the second list is entirely for drawing the animation.

@danh32 danh32 merged commit 65fcb60 into master Apr 19, 2018
@jinatonic jinatonic deleted the wrap_around branch April 19, 2018 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Countdown Timer transition 00:00:20 -> 00:00:19
2 participants