Skip to content

Android Image that has a complex background (i.e. with rounded corners) cannot animate opacity correctly. #1223

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

Closed
hamorphis opened this issue Dec 9, 2015 · 22 comments

Comments

@hamorphis
Copy link
Contributor

Android views which have complex backgrounds (i.e. with rounded corners) cannot animate opacity. The ObjectAnimator will not suffice for such cases and has to be replaced by a ValueAnimator.

@hamorphis hamorphis self-assigned this Dec 9, 2015
@hamorphis hamorphis added this to the 1.6.0 (Under Review) milestone Dec 9, 2015
@hamorphis
Copy link
Contributor Author

I could not find a way to correctly animate the opacity of an android.widget.ImageView which has a background with rounded corners. The opacity is animated, but the rounded corners disappear for some mysterious Android reason. However, here is a very simple workaround which should resolve the problem. Replace the Image widget with any kind of Layout widget, let's say StackLayout. Then bind the style.backgroundImage property to your view model property. In the CSS for the widget add background-size: contain; in order to center the image. In this way, the actual image and the rounded corners will be drawn together and the opacity animation should run.

Exampe:

<StackLayout loaded="imageLoaded" style.backgroundImage="{{ ProfilePic100 }}" row="0" col="0" class="avatar-list-avatar" visibility="{{ ProfilePic100 ? 'visible' : 'collapsed' }}" />
.avatar-list-avatar{
    opacity: 0;
    font-size:35;
    height: 40;
    width: 40;
    border-radius: 20;
    margin-left: 16; 
    horizontal-align: center;
    vertical-align: center;
    background-size: contain;
}

This should do the trick.

The bottomline is that if you want to animate the opacity of an image with rounded corners background, replace it with any kind of layout component and set its backgroundImage instead.

@bradmartin
Copy link
Contributor

@hamorphis - I can't get this to work, what version did you try this on? I even simplified the StackLayout a bit to make sure it wasn't the visibility, but no luck.

    <StackLayout loaded="imageLoaded" style.backgroundImage="{{ ProfilePic100 }}" row="0" col="0"     class="avatar-list-avatar"  />

@hamorphis
Copy link
Contributor Author

I will see what's going on tomorrow.

@bradmartin
Copy link
Contributor

I'll check again but I think I had it work briefly by just setting backgroundImage and not style. property.

I did numerous things and it worked but it also raises another question on how to trigger the animation each time the image comes into view. On some apps I've seen the opacity animation will run as they load in and also as you scroll back up the listview. If you have some thoughts on that let me know. Thanks

@hamorphis
Copy link
Contributor Author

I am currently working on a fix.

hamorphis added a commit that referenced this issue Dec 10, 2015
….e. with rounded corners) cannot animate opacity.
hamorphis added a commit that referenced this issue Dec 10, 2015
Resolved Issue #1223: Android views which have complex backgrounds (i…
@hamorphis
Copy link
Contributor Author

Forget about the StackLayout. You will be able to use the Image after we release version 1.6.

I merged a fix into the master branch. The fix will be released with version 1.6.

As for playing the animation each time the image comes into view, here is a way to achieve this. Imagine that your ListView looks something like this:

          <ListView items="{{ redditItems }}" isScrolling="{{ isScrolling }}" itemTap="listViewItemTap" loadMoreItems="listViewLoadMoreItems" 
                    itemLoading="listViewItemLoading">
            <ListView.itemTemplate>
              <!-- Binding in template property of an component will use the bindingContext provided by the component. -->
              <GridLayout columns="auto, *, auto" rows="auto, 25">
                <Image src="{{ thumbnailImage }}" class="thumbnail" rowSpan="2" loaded="imageLoaded" tag="thumbnail"/>
                <Label text="{{ title || 'Downloading...' }}" textWrap="true" class="title" col="1" colSpan="2" minHeight="50" />
                <Label text="{{ author ? 'by ' + author : '' }}" class="author" col="1" row="1" />
                <Label text="{{ num_comments ? num_comments + ' comments' : '' }}" class="comments" col="2" row="1" />
              </GridLayout>
              <!-- End of tempplate. -->
            </ListView.itemTemplate>
          </ListView>
function imageLoaded(args) {
    console.log(args.object + " loaded for the first time.");
    args.object.opacity = 0;
    args.object.animate({ opacity: 1, duration: 10000 });
}
exports.imageLoaded = imageLoaded;

function listViewItemLoading(args) {
    var gridLayout = args.view;
    gridLayout._eachChildView(function (childView) {
        if (childView["tag"] === "thumbnail" && childView.isLoaded) {
            console.log(args.object + " coming into view.");
            childView.opacity = 0;
            childView.animate({ opacity: 1, duration: 10000 });
            return false;
        }
        return true;
    });
}
exports.listViewItemLoading = listViewItemLoading;
.thumbnail {
    opacity: 0;
    width: 72;
    height: 72;
    margin: 3;
    vertical-align: top;
    border-radius: 36;
}

Here is the app I tested with: https://www.dropbox.com/s/nql8hqaavl5vxba/VSDevApp.zip?dl=0

It uses with our latest tns-core-modules from the master branch.

@hamorphis hamorphis added ready for test TSC needs to test this and confirm against live production apps and automated test suites and removed in progress labels Dec 10, 2015
@hamorphis hamorphis changed the title Android views which have complex backgrounds (i.e. with rounded corners) cannot animate opacity. Android Image that has a complex background (i.e. with rounded corners) cannot animate opacity correctly. Dec 10, 2015
@bradmartin
Copy link
Contributor

I'll try this with the master branch tomorrow. Thanks for the animation code above too! 👍

@hamorphis
Copy link
Contributor Author

Great. Let me know if this works for you so we can close this issue in case it does.

@bradmartin
Copy link
Contributor

@hamorphis would updating the core modules be all I need to do? If so, the images still just appear after the 'duration' has ended. I'm downloading your sample now to try it on a couple devices to see if it works.

@bradmartin
Copy link
Contributor

@hamorphis - just installed the .apk from your sample and it's the same end result. The images just pop into view after the duration.

@bradmartin
Copy link
Contributor

@hamorphis - I've tried running it on a Droid Turbo 2 (5.1.1) and in GenyMotion emulator with 5.0.0

What android version did you try it on?

@hamorphis
Copy link
Contributor Author

Here is what I found which is very weird:

  • Android 4.4.2, API Level 19: works
  • Android 5.0.1, API Level 21: does not work
  • Android 6.0, API Level 19: works

I tested with all kinds of widgets, not just the image, and in version 5 on Android nothing works like on the other versions. What the hell. Versions 4 and 6 work just fine which just kills me. I will see whether I can do something about version 5, but currently I don't know what to "fix" or "hack" so it works on Android 5.

@hamorphis
Copy link
Contributor Author

Another interesting finding. The background completely disappears if I set opacity (i.e. on a button click for example) on any view on Android 5.x. It is not the animation itself that is the problem. I created a separate issue: #1240

@hamorphis
Copy link
Contributor Author

This magic setting seems to fix all visual glitches related to opacity, animations and backgrounds on all API Levels:

        if (view.android) {
            view.android.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null);
        }

where view is the misbehaving widget. Can you try this. Alternatively, you can also try setting LAYER_TYPE_SOFTWARE instead of LAYER_TYPE_HARDWARE.

Call this method in your loaded event handler just before you spin off the animation.

Let me know if this fixes the issue.

@bradmartin
Copy link
Contributor

I'll try it later this weekend. Thanks.

@vakrilov knows all about that setting :), I had a bug with border radius
in android 4.4 and he found setting it to software fixed it. So this could
be a very conflicting issue with styling. If different rendering modes fix
different bugs would it be good to add an android version check and set
this by default or just make the docs point this out very clearly?

On Sat, Dec 12, 2015, 7:00 AM Rossen Hristov notifications@github.com
wrote:

This magic setting seems to fix all visual glitches related to opacity,
animations and backgrounds on all API Levels:

    if (view.android) {
        view.android.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null);
    }

where view is the misbehaving widget. Can you try this. Alternatively, you
can also try setting LAYER_TYPE_SOFTWARE instead of LAYER_TYPE_HARDWARE.

Let me know if this fixes the issue.


Reply to this email directly or view it on GitHub
#1223 (comment)
.

hamorphis added a commit that referenced this issue Dec 12, 2015
… Use this to fix visual glitches: view.android.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null);
@hamorphis
Copy link
Contributor Author

That is a mysterious setting. Only the Android guys know what it does on what API levels. It is not our bug, its theirs. So we would prefer people to set this thing in their projects, we are afraid to hard-code such stuff in the core modules since we might break a lot of people.

What I mean is that if you were not using NativeScript at all and you we writing pure Android app in Java, you would encounter exactly the same issues, so I don't believe it in our hands to fix their bugs or mention them in our docs.

We can't make a huge switch statement with the cartesian product of all possible API Levels, LAYER_TYPES and possible bugs :) :) That woulds be up to the individual projects that people develop.

I can only add a line of code to set it to HARDWARE in case a complex border is being drawn and the API Level is > 18. I hope I will not break anyone else though.

@hamorphis
Copy link
Contributor Author

function onBackgroundOrBorderPropertyChanged(v: view.View) {
    if (!btn) {
        btn = require("ui/button");
    }

    var nativeView = <android.view.View>v._nativeView;
    if (!nativeView) {
        return;
    }

    var backgroundValue = <background.Background>v.style._getValue(styleModule.backgroundInternalProperty);
    var borderWidth = v.borderWidth;
    if (v.borderWidth !== 0 || v.borderRadius !== 0 || !backgroundValue.isEmpty()) {

        var bkg = <background.ad.BorderDrawable>nativeView.getBackground();
        if (!(bkg instanceof background.ad.BorderDrawable)) {
            bkg = new background.ad.BorderDrawable();
            let viewClass = types.getClass(v);
            if (!(v instanceof btn.Button) && !_defaultBackgrounds.has(viewClass)) {
                _defaultBackgrounds.set(viewClass, nativeView.getBackground());
            }

            nativeView.setBackground(bkg);
        }

        bkg.borderWidth = v.borderWidth;
        bkg.cornerRadius = v.borderRadius;
        bkg.borderColor = v.borderColor ? v.borderColor.android : android.graphics.Color.TRANSPARENT;
        bkg.background = backgroundValue;

        if (SDK < 18) {
            // Switch to software because of unsupported canvas methods if hardware acceleration is on:
            // http://developer.android.com/guide/topics/graphics/hardware-accel.html
            nativeView.setLayerType(android.view.View.LAYER_TYPE_SOFTWARE, null);
        }
        else {
            nativeView.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null);
        }
    }
    else {
        // reset the value with the default native value
        if (v instanceof btn.Button) {
            var nativeButton = new android.widget.Button(nativeView.getContext());
            nativeView.setBackground(nativeButton.getBackground());
        }
        else {
            let viewClass = types.getClass(v);
            if (_defaultBackgrounds.has(viewClass)) {
                nativeView.setBackground(_defaultBackgrounds.get(viewClass));
            }
        }

        if (SDK < 18) {
            // Reset layer type to hardware
            nativeView.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null);
        }
    }

    var density = utils.layout.getDisplayDensity();
    nativeView.setPadding(
        Math.round((borderWidth + v.style.paddingLeft) * density),
        Math.round((borderWidth + v.style.paddingTop) * density),
        Math.round((borderWidth + v.style.paddingRight) * density),
        Math.round((borderWidth + v.style.paddingBottom) * density)
    );
}

hamorphis added a commit that referenced this issue Dec 12, 2015
…null); for complex backgrounds on API Level > 18. (Resolves #1223)
hamorphis added a commit that referenced this issue Dec 12, 2015
Reverted the changes made with commit 700818d (Resolves Issue #1223).…
@bradmartin
Copy link
Contributor

@hamorphis - the layerType does fix it. Thanks for figuring that out.

One small issue - if it's not related we can open another issue. Since I want the image to run the animation with the itemLoading event, the animation runs but some images in the list will do the animation and then just "pop" in afterwards. More of a "flash" than a pop I suppose.

My list is a small array right now for testing, maybe 20 user records and the API returns just random items right now so I'll have duplicates. So that might be part of the issue, let me know if you need more info.

@bradmartin
Copy link
Contributor

A little more info, it seems that this second flash effect is once the animation finishes.

I'm trying to find out if it's because of some CSS setting and a conflict with the animation or an event. If I find anything else I'll be sure to note it.

@bradmartin
Copy link
Contributor

It also seems consistent to trigger once the scroll comes to a stop.

@hamorphis
Copy link
Contributor Author

Can you please open a separate issue and attach a small dummy sample project that clearly demonstrates the glitches you observe. I will run it and see whether it is something under our control or not. Thanks in advance.

@hamorphis hamorphis added done and removed ready for test TSC needs to test this and confirm against live production apps and automated test suites labels Dec 17, 2015
@lock
Copy link

lock bot commented Aug 30, 2019

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked and limited conversation to collaborators Aug 30, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants