ListViewDraggingAnimation broken on Android 5 Lollipop
Asked Answered
S

4

29

I'm using ListViewDraggingAnimation by DevBytes, but it seems broken on Android Lollipop developer preview 2 (LPX13D). When I drag a row over other rows, those rows will disappear and become no longer clickable (see below). I tried disabling hardware acceleration for the listview but it didn't have any effect.

Example

Has anyone experienced the same issue? Any hints? Thanks :)

Stenopetalous answered 30/10, 2014 at 9:13 Comment(0)
C
40

I found the problem. It came from this flag. StableArrayAdapter.hasStableId.

It fix all problem from this view on Lollipop.

@Override
public boolean hasStableIds()
{
    return android.os.Build.VERSION.SDK_INT < 20;
}
Calliecalligraphy answered 26/11, 2014 at 16:8 Comment(6)
You should be using constants, like this: Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP (21)Luminance
You're a life saver. Had a similar problem with swipe list by 47deg.com.Discordance
You're a genius, Jeremie! Can you share how you worked this out, or is it too long ago to remember?!Moberg
It takes me a day to find your answer. Thanks so much!Sottish
This is technically wrong. The ids are stable, and they have to be or the system wouldn't work.Musso
This fix did not work for me on Android 5.1, whereas the Tatarize solution did work. The answer begins: "The proper solution is to turn the visibility of that view back on..."Riposte
U
15

To be honest, I don't what causes the problem, but this fix makes no visual errors anymore on any version. And because all that was changed is just visibility of view, I believe it shouldn't create any new functional problems.


Replace this code in function handleCellSwitch() in class DynamicListView:

mobileView.setVisibility(View.VISIBLE);
switchView.setVisibility(View.INVISIBLE);

for

if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.KITKAT){
    mobileView.setVisibility(View.VISIBLE);
    switchView.setVisibility(View.INVISIBLE);
} else{
    mobileView.setVisibility(View.INVISIBLE);
    switchView.setVisibility(View.VISIBLE);
}

I tried tinkering with the code and in the end I found a way how to make it display as it was before. But fixing the problem for Lollipop made the same problem appear on KitKat and previous versions instead. So I applied the fix only for android versions higher than KitKat.

My guess is that these two views gets exchanged in the process for some reason on the new Lollipop version. In effect, one of the views gets always displayed while the other one gets always hidden. Would be nice to know where and what was changed in the lollipop android code though...

Upbraid answered 1/11, 2014 at 14:29 Comment(4)
Thanks for your fix, it mostly works but there is still a glitch. When you drag an item past the bottom of the list, the list starts scrolling down automatically. If you now drag the same item upwards without releasing it, the list starts scrolling up but an empty item appears.Stenopetalous
For me, with Lollipop it works fine in both directions if I remove the else part completely. In other words, for Lollipop don't change visibility of mobileView or switchView.Herniorrhaphy
In Lollipop the change was made to return the same view for the same StableID maximally often. Which means when you recycle views because the notifyDataSetChanged() call comes down, it will give the same views to the same stableIDs when they exist. Usually if you have like five views, they will fill in 0,1,2,3,4 and stay in that order. But, with stable IDs. If view 2 was for a specific stableID it will give you back that view when the content changes. Because it can track the stableIDs and if that object is in the 5th position, it'll still be handed that view for the recycle.Musso
This fix did not work for me on Android 5.1, whereas the Tatarize solution did work. The answer begins: "The proper solution is to turn the visibility of that view back on..."Riposte
M
8

The proper solution is to turn the visibility of that view back on before it's recycled and then turn it back off before it's drawn.

       ((BaseAdapter) getAdapter()).notifyDataSetChanged();

        mDownY = mLastEventY;

        final int switchViewStartTop = switchView.getTop();

        mobileView.setVisibility(View.VISIBLE);
        switchView.setVisibility(View.INVISIBLE);

        updateNeighborViewsForID(mMobileItemId);

        final ViewTreeObserver observer = getViewTreeObserver();
        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            public boolean onPreDraw() {
                observer.removeOnPreDrawListener(this);

replace with,

        mobileView.setVisibility(VISIBLE);
        ((BaseAdapter) getAdapter()).notifyDataSetChanged();


        mDownY = mLastEventY;

        final int switchViewStartTop = switchView.getTop();

        updateNeighborViewsForID(mMobileItemId);

        final ViewTreeObserver observer = getViewTreeObserver();
        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            public boolean onPreDraw() {
                observer.removeOnPreDrawListener(this);

                View mobileView = getViewForID(mMobileItemId);
                if (mobileView != null) mobileView.setVisibility(INVISIBLE);

The stableid solution is wrong. The IDs are stable and telling it that they aren't is in error. It simply kluges the system into recycling the views the old way. Which was in fact a bad way of doing it and was fixed in lollipop. This is a much more correct solution. Rather than predicting how the .notifyDataSetChanged will affect the views and turning on and off the proper elements given whatever version you're using. This one will turn on the visibility in all cases. Then in the predraw listener to find the view again and turns it back invisible, before it's drawn and after it's in its proper state.

And it doesn't matter, if that view is the same one or not, or some future version with some different way of doing it, or a bad adapter that doesn't actually recycle the views, etc. Stable Ids or non-stable ids (flagged not stable, they still have to be actually stable, but that's easily fixable), it's the way to properly do it and future proofed.

The problem here is that which recycled view you're going to get isn't actually clear (they are half trash recycled views after all), not things you can safely store properties in. The behavior was changed in Lollipop to keep stable views actually stable if they can, you could check in the adapter function and the recycled view will likely already have your data because it will give you the same view back again maximally often. Which is something you want, which is why Lollipop does it that way.

The problem here is that the code says mobileView should be made to be visible (the one you're moving), and the switchView should be made invisible (the one you're switching with). But, these are going to be recycled so they are technically ambiguous which one you'll get back, and depends entirely on how the system is recycling the views, and it's completely allowed to change that behavior for the better, and did.

Ps. I bother to check for null because I personally swap the views at the midway point and it can sometimes end up being null if you hit things just right.

    int deltaYTotal = (mHoverCellOriginalBounds.bottom + mHoverCellOriginalBounds.top) / 2
            + mTotalOffset + deltaY;

...

        boolean isBelow = (belowView != null) && (deltaYTotal > belowView.getTop());
        boolean isAbove = (aboveView != null) && (deltaYTotal < aboveView.getBottom());
Musso answered 8/9, 2015 at 2:54 Comment(2)
I actually rewrote basically the entire class. Because it did a bunch of stuff I hated like the coupling of the arraylist and the view and the really crippling requirement of switching everything. youtube.com/watch?v=aae7Kyz3S00Musso
github.com/tatarize/DynamicRecyclingView Basically the original class was pretty screwy. Spent a couple days and fixed it.Musso
C
0

You need to visible the mobileView and switchView before adapter notify. This is work for me.

 mobileView.setVisibility(View.VISIBLE);
 switchView.setVisibility(View.INVISIBLE);

 ((BaseAdapter) getAdapter()).notifyDataSetChanged();

    mDownY = mLastEventY;

    final int switchViewStartTop = switchView.getTop();

    updateNeighborViewsForID(mMobileItemId);
Cohesive answered 4/5, 2016 at 7:44 Comment(4)
If anybody tests this in versions prior to Lollypop they will see that this answer causes all pre-L versions to fail. The original code is wrong that you can use the view to store information while it's being recycled. You must properly set them both visible and then add a predraw listener to set the correct one invisible before its drawn.Musso
The problem is if you try in K, everything will be fubar. I said prior to L. L+ it'll be fine.Musso
i don't think so its happen because i used this one in my application and its support up to 4+ version and its works fine in all version..Cohesive
On implementations of KitKat your code will fubar. AVD a version of Kitkat and you will tell that they go crazy. At least assuming your adapter isn't saying that the stable IDs are false. The implementation of the recycled views was changed in Lollypop. It started giving the StableID view back to the correct index consistently if it was possible. That's why setting StableID to false also tends to fix it, it causes Lollypop to revert to the old code. But, you will always with something here cause an issue. You cannot consistently determine whether the mobileView or switchView is correct.Musso

© 2022 - 2024 — McMap. All rights reserved.