Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fixed Background Image in android without scroll listener

Tags:

android

I'm working on an android library to display an image as a fixed background image. To do this, I'm dynamically ajusting the position of the image every 10ms based on the locationOnScreen. I understand that it's an aweful solution, but I'm here to improve this :)

demo

The issue with this is that there is a glitch when the parent scrollable view is scrolling too fast and the image jump while the other view are moving. (click on the gif for the full demo)

As it's a library, I don't want to add complexity when integrating the lib, meaning no scroll listener, theme or no window override etc.

Solution tried:
- changing the loop delay
- using window background is not possible for a library
- no access to activity theme or similar

handler

    override fun run() {
        fixedBackgroundImageLayout.getLocationOnScreen(locationOnScreen)
        fixedBackgroundImagePlugin.update(locationOnScreen)

        handler.postDelayed(this, 10)
    }

FixedBackgroundImagePlugin#update

    override fun update(locationOnScreen: IntArray) {
        if (backgroundImageFrameLayout == null) {
            return
        }
        yPosition = parentLocationOnScreen[1] - locationOnScreen[1]
        backgroundImageFrameLayout.top = 2 * yPosition - lastYPosition - 10
        lastYPosition = yPosition
    }

the backgroundImageFrameLayout has the image as a background image.

I've also setup a sample repository to help you dig in if wanted.

I'm open to any advice/lead

like image 719
Hugo Gresse Avatar asked Sep 12 '19 13:09

Hugo Gresse


People also ask

How can I fix my background while scrolling?

To keep your background fixed, scroll, or local in CSS, we have to use the background-attachment property. Background-attachment: This property is used in CSS to set a background image as fixed or scroll. The default value of this property is scroll.

Does background-attachment fixed work on mobile?

background-attachment: fixed in CSS, at best, does not work well in mobile browsers, and at worst is not even supported by the most widely used mobile browsers. You can ditch this idea completely and let the background scroll on small screens using media queries.

How does background-attachment fixed work?

The background is fixed relative to the element's contents. If the element has a scrolling mechanism, the background scrolls with the element's contents, and the background painting area and background positioning area are relative to the scrollable area of the element rather than to the border framing them.

Why background-attachment is not working?

It's a known bug in webkit with backface-visibility (and transforms) and any 'fixed' element that has a parent with the backface-visibility property applied will lose its fixed position. There seems to be no fix except to remove the fixed positioned element from that context.


1 Answers

Update: Previous code I posted had some interaction issues that, surprisingly, made the code work but pegged the CPU. The following code is substantially like the previous code, but behaves. Although the new code involves a scroll listener that causes the view to redraw itself, it is self-contained. The scroll listener is needed to detect when the view is re-positioned on the screen. When called, the scroll listener simply invalidates the view.


You can do what you need all within the custom view BackgroundImageFrameLayout. Here is a draw() override that will take care of drawing the background of the FrameLayout that holds the image.

Add the following to the init block:

viewTreeObserver.addOnScrollChangedListener {
    invalidate()
}

Add the following override method to BackgroundImageFrameLayout:

override fun onDraw(canvas: Canvas) { // draw(canvas) may be better choice.
    if (background == null) {
        return
    }

    // Get the location of this view on the screen to compute its top and bottom coordinates.
    val location = IntArray(2)
    getLocationOnScreen(location)
    val imageTop = location[1]
    val imageBottom = imageTop + height

    // Draw the slice of the image that should be viewable.
    canvas.save()
    canvas.translate(0f, -imageTop.toFloat())
    background?.setBounds(0, imageTop, width, imageBottom)
    background?.draw(canvas)
    canvas.restore()
}

Remove everything else that tries to manipulate the background - you won't need it.

Here is a demo of the app with the new onDraw():

enter image description here

I think there may be some other issues with the size of the screen vs. the size of the image, but this is the direction that you should go (IMHO.)

like image 132
Cheticamp Avatar answered Nov 14 '22 17:11

Cheticamp