FIXED: I was incorrectly updating my ListView adapter. see: How to refresh Android listview?
I have been struggling with an incredibly weird bug for too long of a time, and I wonder if any of you have had similar problems.
A detailed explanation of the bug will be less clear than for me to just show you, so I have attached a gif that displays exactly what goes wrong below.
http://giphy.com/gifs/xT9DPGixQnDIEHlVU4
Question 5 is included to show you how things are supposed to wrong. Then, when the CardView and the ListView inside it resize because the amount of options changes at question 6, the following goes wrong:
- The colors of the listview items do not get updated according to their correctness. (should be either green or a light shade of red)
- The checked RadioButton weirdly gets unchecked, right before I click the button.
Note: this bug only appears once upon resizing of the Listview. Everything proceeds to work fine once a similar size optionlist is supplied at the next question
These actions take place upon the second click of the button, which calls the following code
/*
FOR EACH LISTITEM:
> Make button unclickable
> Animate background change according to correctness of answer
*/
for (int i = 0; i < currentQuestion.getOptionList().size(); i++) {
ListView optionList = (ListView) rootView.findViewById(R.id.optionList);
View item = (View) optionList.getChildAt(i);
item.findViewById(R.id.optionRadioButton).setClickable(false);
final LinearLayout llayout = (LinearLayout) item.findViewById(R.id.itemLinearLayout);
int colorTo = getResources().getColor(R.color.lightPrimary);
if (currentQuestion.getOptionList().get(i).equals(currentQuestion.getAnswer())) {
colorTo = getResources().getColor(R.color.correct);
}
int colorFrom = getResources().getColor(R.color.white);
ValueAnimator colorAnimation = backgroundColorAnimator(
colorFrom,
colorTo,
llayout,
i);
colorAnimation.start();
}
// Bottom part of list view is animated SEPERATELY.
// please note that THIS DOES GET UPDATED ACCORDINGLY.
int colorFrom = getResources().getColor(R.color.white);
int colorTo = getResources().getColor(R.color.lightPrimary);
// Also increments question correct counter (important
if (correctAnswerSelected) {
colorTo = getResources().getColor(R.color.correct);
currentExercise.questionCorrectlyAnswered();
}
ValueAnimator anim = backgroundColorAnimator(
colorFrom,
colorTo,
fabContainer,currentQuestion.getOptionList().size()+1);
anim.start();
My hypothesis: Somehow, upon resizing the CardView / RelativeLayout / ListView, the ListView gets reinitiated and I call a different version of it. I suspect this is the cause because the lower part of the CardView (a FAB within a FrameLayout) does change color accordingly.
Any and all help is appreciated, I am at a complete loss with this question
Thanks in advance.
Related
So I am facing a weird bug I cannot explain - I cannot even reproduce it sometimes.
Basic context:
I have an application, which lists objects. Every object has a name and a point value. For every object, the addCustomSpinner function creates a "ticket" (a custom view, kind-of-spinner) and shows them in a scrollview so the user can select the one needed. There are four different 'containers' for four different kind of objects - so the layout can be populated with four kind of "ticket" package.
The data for the objects are collected from a database. The addCustomSpinner is called with a for cycle for every object in the database, and - Important - before the for method, the Layout it populates with the tickets is cleared (removeAllViews).
Inside addCustomSpinner, everything is created as "new" - like the button in question.
addCustomSpinner creates this button and adds a new onClickListener. Inside onClickListener, a new boolean is created - this is used to show a different animation when the button is clicked again. On first click (boolean = true), the arrow turns 180 degrees and faces upwards, on second click (boolean = false) the arrow turns 180 degrees and faces downwards. Works like a charm, until...
The bug I am facing:
Sometimes - as I already mentioned, not every time - if I click the button for one "ticket", then leave it 'opened' and click on an another one, and leave it 'opened' also, THEN I choose to populate the layout with a different kind of "ticket" package - The arrow faces upwards by default on every ticket in every package! Sometimes - again, just sometimes - with the same pattern I can turn it back, but it happens just "by accident".
I don't understand how the animation and state of the buttons can be connected, if every created ticket is new, every button is new, every onClickListener is new, and every boolean inside onClickListener is new. And if these are connected somehow, then why can that be that every behavior is "unique" for the buttons, nothing else shows any connection - even this is just a "sometimes" bug, a pretty rare one.
Can anybody help me why this happens?
What I tried:
Well, tried to trace the issue - but since it happens just by accident, I have no clue, I just searched if I can do anything else than the boolean to add different animation for the clicks. Sadly using ObjectAnimator is not a good solution for me - not the same result at least, since my animated arrow not only rotates, but it also changes its color. Shapeshifter seemed like a good idea to create animations easily, but now as I see it, maybe a simple rotation will be my ultimate solution.
Here's the code for the button:
customButton.setOnClickListener(new View.OnClickListener() {
boolean isCustomButtonClicked = true;
#Override
public void onClick(View v) {
if (isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue_back);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(View.VISIBLE);
isCustomButtonClicked = false;
} else if (!isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(GONE);
isCustomButtonClicked = true;
}
}
});
EDIT:
The full addCustomSpinner():
private void addCustomSpinner(Routes mRouteItemToAdd, String placeName) {
//creating a new View for my custom layout created in xml
View customRoutesView = new View(this);
LinearLayout.LayoutParams customViewParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
customRoutesView.setLayoutParams(customViewParams);
customRoutesView = LayoutInflater.from(this).inflate(
R.layout.custom_view_layout, routeLayout, false
);
//Setting up the views inside the custom view
ImageView imageViewDiffImage = customRoutesView.findViewById(R.id.routeDiffImageView);
TextView textViewRouteName = customRoutesView.findViewById(R.id.routeNameTextView);
TextView textViewRouteDiff = customRoutesView.findViewById(R.id.routeDiffTextView);
ImageButton customButton = customRoutesView.findViewById(R.id.customButton);
RadioButton climberNameOne = customRoutesView.findViewById(R.id.climberNameOne);
RadioButton climberNameTwo = customRoutesView.findViewById(R.id.climberNameTwo);
Button climbedItButton = customRoutesView.findViewById(R.id.climbed_it_button);
RadioGroup climberNameRadioGroup = customRoutesView.findViewById(R.id.climberNameRadioGroup);
RadioGroup climbingStyleRadioGroup = customRoutesView.findViewById(R.id.styleNameRadioGroup);
RelativeLayout routeWhoClimbed = customRoutesView.findViewById(R.id.routeWhoClimbedRelativeLayout);
imageViewDiffImage.setImageResource(R.mipmap.muscle);
textViewRouteName.setText(mRouteItemToAdd.name);
textViewRouteDiff.setText("Difficulty: " + (int) mRouteItemToAdd.difficulty);
climberNameOne.setText(climberName1);
climberNameTwo.setText(climberName2);
routeWhoClimbed.setVisibility(GONE);
//Here comes the button with the animated image
customButton.setOnClickListener(new View.OnClickListener() {
boolean isCustomButtonClicked = true;
#Override
public void onClick(View v) {
if (isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue_back);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(View.VISIBLE);
isCustomButtonClicked = false;
} else if (!isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(GONE);
isCustomButtonClicked = true;
}
}
});
//Button, works like an 'OK' or something, and I have no
//problem with this
climbedItButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int checkedNameButton = climberNameRadioGroup.getCheckedRadioButtonId();
int checkedStyleButton = climbingStyleRadioGroup.getCheckedRadioButtonId();
RadioButton checkedNameRadioButton = (RadioButton) findViewById(checkedNameButton);
RadioButton checkedStyleRadioButton = (RadioButton) findViewById(checkedStyleButton);
String checkedName = (String) checkedNameRadioButton.getText();
String checkedStyle = (String) checkedStyleRadioButton.getText();
addClimbToDatabase(user.getUid(), checkedName, mRouteItemToAdd, placeName, checkedStyle);
}
});
//And finally, I add this new "ticket" with the custom view to the layout i want to show it. Again, this also works like a charm, no problem here.
routeLayout.addView(customRoutesView);
}
Ultimately, I did not manage to understand the problem throughly, but I was able to eliminate it.
So during my fixing tries I narrowed down the problem to the animated drawable state - credit to #avalerio for his pro tip, but the answer wasn't addig an id to the button. I think somehow and sometime, the state of the first animation (turning the arrow 180 degrees) stuck in the end position - causing the other views using this animatedDrawable showing it in end position on start.
.reset() did not help, since it resets the animatedVectorDrawable object, not the animation xml drawable state. My solution is a kind of workaround, but it is working: when the custom-view 'ticket' is created with the animated-drawable-imagebutton, I set the imageResource of the button to a not-animated xml drawable - this drawable is basically the start position of my animated-drawable. This way, when the 'tickets' are generated, the imagebutton is 'hardcoded' in the start position.
Not elegant, but works. BUT(!) I would really appreciate if someone could explain to me how this weird behavior is possible - just sometimes, randomly, with no pattern I can reproduce intentionally.
I know how to do this for 2 images but what about 3 images.
public void fadeBardock(View view) {
ImageView bardock = (ImageView) findViewById(R.id.bardock);
ImageView goku = (ImageView) findViewById(R.id.goku);
ImageView gohan = (ImageView) findViewById(R.id.gohan);
bardock.animate().alpha(0 f).setDuration(3000);
goku.animate().alpha(0 f).setDuration(4000);
gohan.animate().alpha(1 f).setDuration(5000);
}
After clicking on button I'm directly getting the third image without getting the second image.
Try this code:
bardock.animate().alpha(0f).setDuration(3000);
goku.setAlpha(0f);
goku.animate().alpha(1f).setDuration(3000).setStartDelay(1500);
goku.animate().alpha(0f).setDuration(3000).setStartDelay(4500);
gohan.setAlpha(0f);
gohan.animate().alpha(1f).setDuration(3000).setStartDelay(6000);
You can change duration and start delays to achieve your optimal result.
On More than one imageviews,
1. Create an array of ImageView.
2. Initialize the views in it.
3. assign unique id's to each view in same loop.
[ Another thing is, you can initialize the views in one for loop in onCreate() and then on button click, apply another for loop for animation.]
Now, using an index of array, assign an animation to each view with delay from previous views.
Start animation after the loop and check.
Hope it helps.
I have a problem that I have been unable to solve in a way that I am very happy with.
I have a view that I am dragging and dropping into a list. That list is created using a recyclerView. The drag object works fine, and the recyclerView's items can all receive the events no problem. Now I want to make the list scroll as the user drags their finger close to the top or bottom of the list. My first step was to add a dragEvent listener to the recyclerView, and attempt to start scrolling each time I got a location near the top or bottom edge. So, my DragEvent.Location case looks something like this:
case DragEvent.ACTION_DRAG_LOCATION: {
removeDragScrollCallBack();
float y = event.getY();
final int scrollAreaHeight = v.getHeight()/4;
final int delayMills = 16;
int scrollAmount = 0;
if (y > v.getHeight() - scrollAreaHeight) {
scrollAmount = 10;
} else if (y < scrollAreaHeight) {
scrollAmount = -10;
}
if (Math.abs(scrollAmount) > 0) {
final int finalScrollAmount = scrollAmount;
dragScrollRunnable = new Runnable() {
#Override
public void run() {
if (canScrollVertically(finalScrollAmount)) {
scrollBy(0, finalScrollAmount);
if (dragScrollHandler != null && dragScrollRunnable != null) {
dragScrollHandler.postDelayed(this, delayMills);
}
}
}
};
dragScrollRunnable.run();
}
return true;
}
It kinda works. Things scroll in the right direction. It seems to sputter a bit though, and generally not scroll very smoothly. Additionally, the drag and drop drop event sometimes doesn't make it to the children while the recycler view is still scrolling.
So, I went to the google example of doing a similar thing in a using a list view - link. I modified the code they used for their list view and tried to handle my recyclerView in a similar manner. This had even poorer results for me.
I have tried various other alterations of these techniques, and swapped to using the smoothScroll function instead of the standard scroll function, but I'm not too happy with any of the results.
Does anyone have a good solution for how to handle this?
Update: I now believe that many of my problems with this functionality are due to the drag listener being fairly unreliable. At sometimes the recycler fails to get events when it's children are receiving events.
Turns out the drag listener on a view is not terribly reliable. At random times as I moved my finger around the screen, the drag listener wouldn't recieve all of the events. I believe the reason for this was the way that the children of the recyclerView were also recieving the on drag callbacks. The solution was to do what I had tried originally, but through a listener on the fragment itself. Now when I get an event, I check the coordinates to see what view it is in, and then convert it to local coordinates for that view. Then I can determine exactly how I need to handle it.
I'm trying to implement an animation which crossfades between 2 layout, with a layover between them. The first layout is the main layout, the second is a simple linear layout with textView and red background (your classic "Wrong!" screen if you will...). My goal is to animate a quick transition between the mainLayout and wrongLayout, have the program wait for a short period of time while displaying the wrongLayout and then automatically return to mainLayout. This turns out to be extremelly hard, I've tried using monitors, Thread.sleep() etc. but what I get is that the program waits before starting the animation and THEN performs it without any layover.
My code is as follows:
In the main method-
LinearLayout wrongLayout = (LinearLayout) findViewById(R.id.wrong_layout);
RelativeLayout mainLayout = (RelativeLayout) findViewById(R.id.main_layout);
int animationDuration = getResources().getInteger(
android.R.integer.config_longAnimTime);
crossfade(wrongLayout, mainLayout, animationDuration);
/* This is where I want it to wait for 1 second */
crossfade(mainLayout, wrongLayout, animationDuration);
and the crossfade method-
private void crossfade(View fadeInLayout, final View fadeOutLayout,
int animationDuration) {
// Set the content view to 0% opacity but visible, so that it is visible
// (but fully transparent) during the animation.
fadeInLayout.setAlpha(0f);
fadeInLayout.setVisibility(View.VISIBLE);
// Animate the content view to 100% opacity, and clear any animation
// listener set on the view.
fadeInLayout.animate()
.alpha(1f)
.setDuration(animationDuration)
.setListener(null);
// Animate the loading view to 0% opacity. After the animation ends,
// set its visibility to GONE as an optimization step (it won't
// participate in layout passes, etc.)
fadeOutLayout.animate()
.alpha(0f)
.setDuration(animationDuration)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
fadeOutLayout.setVisibility(View.GONE);
}
});
}
Thanks a lot...
Ok, so I've found this post which deals with just about the same problem-
Why the Sleep executes first and than the code above it in android?
I've changed the appropriate execution function and now it seems to work properly, though I'm still puzzled by the fact that I cannot cause the code to stop at a designated location for a specific amount of time. Oh well...
I am trying to make an app that lets the user store and saves contacts. It can save, but it has problems listing.
I have a for loop that runs through the database and prints a set of data for each contact (each row), an image (actually its a string because it passes the path) and a string. It prints them in a scrollview with a linear layout for each contact (each contact has a linear layout of its own, so that i can let one contact occupy a row each). The images come out, however, the textviews are nowhere to be found.
Using log.d(textview.getText()); it confirms that the textviews are created and take up space.
http://chesnutcase.heliohost.org/derpbox/itv1.png
Two "contacts" with names, not printed out. The space inbetween is presumbly by the textview.
http://chesnutcase.heliohost.org/derpbox/itv2.png
Another two "contacts", but without names. The dont have a space between each other. Or at least, a significantly smaller space.
Code:
DatabaseHandler db = new DatabaseHandler(this);
int a = (int) (long) db.countRows();
LinearLayout theLayout = (LinearLayout)findViewById(R.id.contactsList);
for(int i = 0;i<a;i++){
ImageButton image = new ImageButton(this);
int density=getResources().getDisplayMetrics().densityDpi;
LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(density,density, 0.5f);
image.setLayoutParams(vp);
image.setScaleType(ImageView.ScaleType.FIT_END);
int b = a - i;
try {
image.setImageBitmap(decodeUri(Uri.parse(db.getContactData("photo_path")[i])));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
theLayout.addView(image);
TextView tv = new TextView(this);
tv.setText(db.getContactData("name")[i]);
Log.d("UserLog","name is " + db.getContactData("name")[i]);
Log.d("UserLog","textfield contains " + tv.getText());
LinearLayout.LayoutParams vp2 = new LinearLayout.LayoutParams(0,0,1f);
tv.setLayoutParams(vp2);
tv.setBackgroundColor(Color.RED);
theLayout.addView(tv);
}
Any solutions? Thanks in advance
Double check which orientation you've applied to the LinearLayout of your contacts list.
You are setting bad LayoutParams to your TextView. You're making your TextView 0px by 0px with a weight of 1.
LinearLayout.LayoutParams vp2 = new LinearLayout.LayoutParams(0,0,1f);
tv.setLayoutParams(vp2);
Try using one of the MATCH_PARENT or WRAP_CONTENT constants. They're listed here: http://developer.android.com/reference/android/view/ViewGroup.LayoutParams.html#MATCH_PARENT
If you want your TextView to take up the remaining width of the screen I would leave the weight as 1, the width as 0, but you need to set the height to a constant like WRAP_CONTENT.
You're also setting the size of your ImageView to the device screen density (which is a constant) instead of setting a scaling size based off your screen density.
You probably need to call requestLayout(); in order to update the current view layout.
theLayout.requestLayout();
Also it seems you are creating a view with 0 width and 0 height with that layout params:
LinearLayout.LayoutParams vp2 = new LinearLayout.LayoutParams(0,0,1f);
It is better to use ListView for such list.
You will need to write only one Adapter, that's represent logic for creating one row on list.
I would recommend using a CursorAdapter and a layout xml file, this way you can design it to look exactly how you want, and preview it. It is a lot easier than setting all those fiddly LayoutParams
If you have to create them dynamically you may find the text colour is the same as the background, try setting it to something visible like bright red for testing. If you still don't see the text it may be that it's visibility isn't set to View.VISIBLE finally the layout may not be the correct size, a handy tip for this is set the background to a suitably eye catching colour, even if there is no text you should see a shaded block.