This post is about ensuring that a live wallpaper app scrolls properly on all Android devices. This is huge issue on certain launchers, as they are not calling the onOffsetsChanged() function correctly, which notifies the wallpaper app that the home screen was scrolled. Luckily, there is a workaround.
We’re working on a live wallpaper called HPSTR, which delivers filtered images with a sensation of motion. After starting private beta, we stumbled upon a weird issue. Everything was OK while we were testing on Nexus devices, but things got strange when we tried it on a Samsung Galaxy phone. Namely, the background stood still when we flipped through the homescreen. We experienced the same on an HTC phone.
A few minutes of debugging showed that the onOffsetsChanged() method was not called properly on the Samsung device. This function is responsible for signalling the wallpaper app that the home screen pages were scrolled, as a result of the launcher calling the setWallpaperOffsets() method. Every time the home screen is scrolled, this function should be fired, informing the current wallpaper about the offset (both in pixels and percentage) and the number of pages in the launcher (through the
xOffsetStep parameter). This doesn’t work with Samsung and HTC default launchers - after they were replaced with something else from the Play Store, the issue disappeared, the behaviour of the wallpaper was OK again. Read more here.
Since our app depends on motion, we needed to figure this out. Luckily, there is a best-effort solution (besides changing the launcher of course).
this is how HPSTR Live Wallpaper looks
Working without onOffsetsChanged()
The idea is to process touch events in the
onTouchEvent() method if
onOffsetChanged() is not working. I know, there are a number of bad things with this approach (see the end of this post) - this is a best-effort workaround.
The first task is to determine if
onOffsetsChanged() is working OK. The launchers which don’t function properly tend to call this function with an
xOffset of 0.0 or 0.5 - this is the cornerstone of the solution. There’s no guarantee that this will work with any launcher. This part is based on this StackOverflow-thread:
isOnOffsetsChangedWorking variable tells if
onOffsetsChanged() is working OK. If not, the app will process touch events by passing the MotionEvent instance to a GestureDetector:
Set offsetChangedWorking to false in
onResume(), to make sure that the wallpaper can detect launcher changes.
Implementing the GestureDetector
The idea behind the implementation is to imitate the
xOffset param of the
onOffsetChanged() method by assembling a similar variable (let’s call it the same). This way there’s no need to modify the code responsible for translating the background, it can take this value as parameter.
xOffset variable represents a percentage, amounting to where the current position is relative to the whole homescreen. By scrolling through the pages, the value goes from 0.0 (first page) to 1.0 (last page). This is based on the number of pages - for example, if the launcher has 3 screens, the
xOffset for each is 0.0, 0.5 and 1.0, with fractions in between. The problem here is that if the
onOffsetsChanged() method is out of the picture, there’s no way of telling the number of pages, so it’s time to improvise. You can roll with a fixed amount, or make it adjustable in the settings (this is what we ended up doing). Then it’s just some basic programming, each full-screen swipe scrolls the wallpaper by [width of the screen] / [number of pages]. To prevent overscrolling, keep the value between 0 and 1.
The onScroll() function is responsible for detecting if the user is scrolling the homescreen.
The onFling() method takes care of swiping between pages. The following implementation finds the closest page based on the direction of the fling and the current
xOffset, then smoothly scrolls the wallpaper to position using a ValueAnimator. The duration cannot be calculated properly, since it varies by the speed of the swipe movement and the launcher itself; according to our experiments, 150 ms is a good compromise.
Detecting double tap events
Muzei, one of my favourite live wallpapers can be triggered by double-tapping the homescreen, which gave us an idea to open the settings with the same gesture. This can also be active if you choose not to support launchers without a working
This can be achieved easily, just override
onDoubleTap() in the GestureDetector, and start the Activity if an event is detected. And also, since you’re starting the Activity from a Service, don’t forget to add the
Enabling infinite scrolling
Some launchers have a feature called infinite scrolling, meaning if you scroll further than the last page, you’ll end up on the first one, and vice versa. This can mess up the background image if you scroll in the same direction for too long.
Unfortunately, there’s no way of detecting this, so we added a checkbox to toggle it. In code, all you have to do is to make the 0-1 value limit conditional in the methods of the GestureDetector.
This solution is far from perfect, but then again, without an operational onOffsetsChanged() method, there’s no way of determining the number of pages, the precise scrolling offset, if the home button is pressed, or if infinity scrolling is enabled. However, supporting it is fairly straightforward, a well-coded GestureDetector can take care of most issues. The sample code is available via this gist. And if you’re interested, try the app!