Android - TransitionDrawable starting in 'end' state flickers when initially drawn - android

I have a RecyclerView holding a list of Artists. An artist can be marked as a favorite or not a favorite.
There's a heart to represent this state, and I have a transition drawable, where I'm going from a outlined heart, to a filled in heart based on if the artist in question has been marked as a favorite. The animation itself is working correctly and smoothly transitions between the two when the user clicks on the heart, and looks beautiful.
TransitionDrawable td = new TransitionDrawable(new Drawable[]{
ContextCompat.getDrawable(getActivity(), R.drawable.favorite_border),
ContextCompat.getDrawable(getActivity(), R.drawable.favorite)});
artistViewHolder.image.setImageDrawable(td);
if (artist.isFavorite()) {
td.startTransition(0);
}
In the onClick for the item, I call either .reverseTransition(300) or .startTransition(300) depending on the next state.
I'm running into problems anytime the view is created (1st screen load) and I need to start in the 2nd position (favorite). The initial load you can see it flicker from the empty heart to the filled heart, even though my animation time is set to 0 when it's created. This also happens anytime the list gets invalidated such as if it gets filtered into a smaller set of artists.
I guess since it needs to do things and isn't actually just setting the drawable, 0 isn't actually "instant". I can't find any other way to start in the "end" position though, other than to reverse the starting position of the images in the transition drawable itself.
If I do that though, start/reverse are going to behave differently on a heart by heart basis. At best, I would need to extend the transition drawable, and override the start/reverse/reset methods to take into account which initial state it's in, and that seems kind of hacky?
Am I missing something obvious to start in the end position but not cause that flicker?
Thanks!

You don't migtht remove flicker on td.startTransition(0);
So, you need change order in your Drawables array.
For example
Drawable favorite_border = ContextCompat.getDrawable(getActivity(), R.drawable.favorite_border);
Drawable favorite = ContextCompat.getDrawable(getActivity(), R.drawable.favorite);
TransitionDrawable td = new TransitionDrawable(isFavorite
? new Drawable[]{favorite, favorite_border}
: new Drawable[]{favorite_border, favorite});
artistViewHolder.image.setImageDrawable(td);
This will work provided that you do this in onBindViewHolder

Unless there's a better way to do this, I've gone ahead and extended it, and flipped the start/reverse call based off the initial state.
public class MyTransitionDrawable extends TransitionDrawable{
private boolean initialFavorite = false;
public MyTransitionDrawable(Drawable[] layers) {
super(layers);
}
public MyTransitionDrawable(Drawable[] layers, boolean initialFavorite) {
super(layers);
this.initialFavorite = initialFavorite;
}
public boolean isInitialFavorite() {
return initialFavorite;
}
#Override
public void startTransition(int durationMillis) {
if(!initialFavorite){
super.startTransition(durationMillis);
}else{
super.reverseTransition(durationMillis);
}
}
#Override
public void reverseTransition(int durationMillis) {
if(!initialFavorite){
super.reverseTransition(durationMillis);
}else{
super.startTransition(durationMillis);
}
}
}
`

Related

How to change color of Rects instantly when touched?

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

How can I change the background of views inside a recyclerview for certain items?

So essentially, I have a recyclerview that has a list of levels. But essentially, I want certain items to have a black colored background to suggest that they are not locked yet.(kind of like in video games)
my current method:
#Override
public void onBindViewHolder(BracketsAdapter.ViewHolder viewHolder, int position) {
// Get the data model based on position
Bracket bracket = bracketsList.get(position);
int color = UserPreferences.getInstance().getBracket();
// Set item views based on your views and data model
TextView textView = viewHolder.nameTextView;
textView.setText(bracket.toString());
Log.d("Bracket level", ""+bracket.getId());
if(color < bracket.getId()) {
textView.setBackgroundResource(R.color.black); // suggests level is locked
}
}
The issue with this method, is that it works initially but the moment I scroll down and back up, items that should not be colored end up being colored. Why is this? Is there a better way of implementing this?
Recycle view always reuses the old view so you are getting that problem, To solve the problem, you should set color every time calling bindViewHolder, So try to do like this,
if(color < bracket.getId()) {
textView.setBackgroundResource(R.color.black); // suggests level is locked
} else {
textView.setBackgroundResource(R.color.defaultcolor); // default color
}
this will work fine.

RecyclerView re-animate views on scrolling

I want animate view on click of it. I am animating that view using AnimatorSet
But when user scroll at that time state of animation is not maintain or not clearAnimation properly. It will animate other item's icon.
anyone has idea on it?
animation method:
public static void likeAnimation(Object object) {
if (object == null) {
return;
}
AnimatorSet set = new AnimatorSet();
Object myView = object;
set.setInterpolator(new LinearInterpolator());
set.playTogether(
ObjectAnimator.ofFloat(myView, "rotation", 0, 360)
);
set.setDuration(500).start();
}
RecyclerView code:
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
holder.imageView_like.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
likeAnimation(v);
}
}
You're just in time with this question, and I hope I'm not too late. An idea here is very simple. You have to keep in mind that your RecyclerView reuses every single ViewHolder which has ever been created. Usually, it creates about 7~8 items, but it depends on screen size.
So, in your particular case if you want to animate an item upon click, you have to track the state of every item in your RecyclerView. Basically, you can have three states:
Enter state or your very animation. When the user clicks on some item from the list, you start animating that item, and in the end of animation switch the state of the item to Animated state.
Animated state. This is the state of an item that has been animated, and doesn't need this animation again, so when the user scrolls up/down you just need to set a view into the animated state. In your case, you can try the following code:
private void animatedState(View view){
//perform the same action but without animation
view.setRotation(360);
}
Default state or normal state. That is View without rotation, just a normal state. For instance,
private void defaultState(View view){
view.setRotation(0);
}
Also you may want to animate the items back upon click, if yes, you have to add one more state which is an Exit state, and then switch from that state to the Default state.
You can find the entire implementation of this idea by this link.

ImageView rotates only once using AccelerateDecelerateInterpolator

I am trying to rotate an image by 360 degree using AccelerateDecelerateInterpolator. I have included the code to rotate the image in an onclickfunction of a button. When i press the button for the first time, the image rotates. However, when i press it the next time, nothing happens.
public void displaySpinResult_Spinner(View view) {
arrow.animate().rotation(360).setInterpolator(new AccelerateDecelerateInterpolator()).start();
}
This is probably because the rotation value is retained. When you specify rotation(360) for the second time, the View is already rotated by 360degrees, so nothing happens.
You can either try rotation(arrow.getRotation() + 360), or use the rotationBy() method instead.
I think this is the solution:
public void displaySpinResult_Spinner(View view) {
arrow.animate().rotation(360).setInterpolator(new AccelerateDecelerateInterpolator()).start();
arrow.animate().rotation(360).setInterpolator(new AccelerateDecelerateInterpolator()).reset();
}

Autoadvancing gallery animation

I have a Gallery widget set up to auto advance every 10000ms. However the transition between views is instantaneous and I would prefer to have a transition.
My advancing method is like so:
private void tickerAdvance() {
int selectedItemPosition = mTickerGallery.getSelectedItemPosition();
if (selectedItemPosition + 1 == mTickerGallery.getCount()) {
mTickerGallery.setSelection(0, true);
} else {
mTickerGallery.setSelection(selectedItemPosition + 1, true);
}
}
I was under the impression that setting the animation to true would cause it to animate between states. In my XML I've also added animationDuration="500", however the transition still pops between states instantly.
The problem is because you have used the setSelection() which obviously won't give you the feel of an Gallery being scrolled. SO instead of using setSelection() you have to override the onScroll() of gallery.
Here is how you do it.
Assuming that you have made the necessary steps for auto advancing periodically, now do this code.
MotionEvent e1, e2;
gallery.onScroll(e1, e2, scroll_limit , 0); //scroll limit is your integer variable for scroll limit.
The answer I came up with is that the Gallery API is terribly non-extensible. There are multiple package private or private methods that would unlock this functionality without a kludge including moveNext, FlingRunnable.startwithdistance, and scrollToChild. Alas, they are all unavailable.
Instead the best answer I was able to come up with was to override setSelection to get all the behaviors I needed. In the setSelection method I reverse the deceleration algorithm to calculate a velocity for a calculated distance and then call onFling. This is a nasty kludge could be made a ton better by making any of the above methods protected or public (I can't fathom a reason why at least moveNext and scrollToChild shouldn't be).
i had a similar task to make item to item animation and i decided to choose gallery, so i tried a lot of things, but the best way is to subclass gallery and add this:
boolean avoidSound = false;
public void showNext() {
avoidSound = true;
this.onKeyDown(KeyEvent.KEYCODE_DPAD_RIGHT, null);
}
public void playSoundEffect(int soundConstant) {
if (avoidSound) {
avoidSound = false;
} else {
super.playSoundEffect(soundConstant);
}
}
just call showNext() and it will do an animation

Categories

Resources