7

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

Hugo Gresse
  • 17,195
  • 9
  • 77
  • 119
  • 2
    Can you just put a ScrollView on top of an ImageView? – charles-allen Sep 16 '19 at 08:59
  • I am unable to understand what exactly you're trying to achieve. Could you please add an example of what you're trying to achieve? – Birju Vachhani Sep 16 '19 at 09:19
  • For a full demo (with the issue), check https://raw.githubusercontent.com/HugoGresse/FixedBacgroundImageAndroid/master/demo-bugy.gif @AjahnCharles but how will the scrollview react to a parent scroll not directed into this one? I mean the user scroll on the whole ScrollView with the text and the image. I'm only responsible of the image itself. – Hugo Gresse Sep 16 '19 at 09:26
  • **I'm working on an android library to display an image as a fixed background image.** you wants only text should be scroll not image ? right? Image should be remain at its fixed position? – Faizzy Sep 16 '19 at 09:32
  • @MohdFaizan yes your right, the demo actually show the desired behavior and also show the issue I'm facing. https://raw.githubusercontent.com/HugoGresse/FixedBacgroundImageAndroid/master/demo-bugy.gif – Hugo Gresse Sep 16 '19 at 09:35
  • The image which is in the background will take the whole screen height or 200dp as you mentioned in your layout file on git repo? – Faizzy Sep 16 '19 at 09:49
  • the whole screen height, but only 200dp is displayed of it. – Hugo Gresse Sep 16 '19 at 11:37
  • @AjahnCharles I've tested with wrapping the image in a ScrollView and controll the scroll using ScrollView#scrollTo() the glitch is reduced in favor of some delay (with is still better) but I've still an issue when the fling start, you can see the image scroll a little too much and less go back to the last scroll: https://github.com/HugoGresse/FixedBacgroundImageAndroid/blob/scrollview/fixedbackground-scrollview2.mp4 Also, everything in on the branch scrollview on https://github.com/HugoGresse/FixedBacgroundImageAndroid/tree/scrollview – Hugo Gresse Sep 16 '19 at 13:07
  • @Hugo Gresse - Not wrapped. I meant ImageView as 1st child, and ScrollView as 2nd child. The ScrollView would be on top of the ImageView. I theorised that you can just see through the ScrollView. If that's possible, you probably don't need a library... – charles-allen Sep 16 '19 at 14:03
  • 1
    understood, the thing is that it's really a library that does much more, managing the image is just a specific part. So it cannot change the layout outside of ti's designed container. – Hugo Gresse Sep 16 '19 at 14:13

1 Answers1

2

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.)

Community
  • 1
  • 1
Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • Well done, using the onDraw did fix the issue while still using the runnable. Using the scrollListener on the viewTreeObserver allow the removal of the postDelayed. Side note, can you explain a little why using the .save() & .restore() on the canvas? – Hugo Gresse Sep 23 '19 at 07:26
  • see https://github.com/HugoGresse/FixedBackgroundImageAndroid/pull/1/files#diff-c4ff4be1502f019543cb1edf036773c8R25 – Hugo Gresse Sep 23 '19 at 07:31
  • See [here](https://stackoverflow.com/a/25497008/6287910) for a good explanation about save/restore. The view is smaller than the image, but the slice selected from the image does not fall within the view but somewhere outside of it. To correct this, the canvas is slid down so the view and the image slice overlap. Save and restore simply saves and restores the canvas position and does not effect the drawing. (Basically undoes the _translate_.) – Cheticamp Sep 23 '19 at 11:18
  • On a side note, consider resizing the image in [onSizeChanged()](https://developer.android.com/reference/android/view/View.html#onSizeChanged(int,%20int,%20int,%20int)). _onMeasure()_ may be called more than once per layout. You only care to resize the bitmap when the view size changes. – Cheticamp Sep 23 '19 at 11:18
  • perfect, thanks Cheticamp, you've well earned your bounty! Thanks again. – Hugo Gresse Sep 23 '19 at 13:09