#5 Floating Windows on Android: Moving Window

Learn how to use floating windows in your Android apps. The fifth lesson teaches you how to make your windows movable.

Have you ever wondered how to make those floating windows used by Facebook Heads and other apps? Have you ever wanted to use the same technology in your app? It’s easy, and I will guide you through the whole process.

I’m the author of Floating Apps; the first app of its kind on Google Play and the most popular one with over 8 million downloads. After 6 years of the development of the app, I know a bit about it. It’s sometimes tricky, and I spent months reading documentation and Android source code and experimenting. I received feedback from tens of thousands of users and see various issues on different phones with different Android versions.

Here’s what I learned along the way.

Before reading this article, it’s recommended to go through Floating Windows on Android 4: Floating Window.

In this article, I will teach you how to make the window movable by dragging its header.

Updating Layout

In the previous article, we introduced how to add views to WindowManager along with their LayoutParams. However, from the moment the window is added, changes made to LayoutParams are not reflected.

To apply changes, it’s necessary to call method updateViewLayout of WindowManager. Therefore, we need to implement two additional methods for our Window class for changing the window position.

private fun setPosition(x: Int, y: Int) {  
  windowParams.x = x  
  windowParams.y = y  
  update()  
}  

private fun update() {  
  try {  
    windowManager.updateViewLayout(rootView, windowParams)  
  } catch (e: Exception) {  
    // Ignore exception for now, but in production, you should have some  
    // warning for the user here.  
  }  
}

Of course, the very same principle can be used to change the window’s size, opacity, etc.

Custom OnTouchListener

For moving the window, let’s implement a custom OnTouchListener that allows us to track the initial point’s movement. Also, let’s keep the basic functionality - clicks & long-clicks - to work in the expected system-like way.

Our implementation doesn’t support multitouch, but it’s good enough to demonstrate how to move the window.

The source code of our new DraggableTouchListener is here:

class DraggableTouchListener(
    context: Context,
    private val view: View,
    private val initialPosition: () -> Point,
    private val positionListener: (x: Int, y: Int) -> Unit
) : View.OnTouchListener {

  private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
  private val longClickInterval = ViewConfiguration.getLongPressTimeout()
  private var pointerStartX = 0
  private var pointerStartY = 0
  private var initialX = 0
  private var initialY = 0
  private var moving = false
  private var longClickPerformed = false
  private var timer: Timer? = null

  init {
    view.setOnTouchListener(this)
  }

  private fun scheduleLongClickTimer() {
    if (timer == null) {
      timer = Timer()
      timer?.schedule(timerTask {
          if (!moving && !longClickPerformed) {
              view.post {
                  view.performLongClick()
              }
              longClickPerformed = true
          }
          cancelLongClickTimer()
      }, longClickInterval.toLong())
    }
  }

  private fun cancelLongClickTimer() {
    timer?.cancel()
    timer = null
  }

  override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {

    when (motionEvent.action) {

        MotionEvent.ACTION_DOWN -> {
            pointerStartX = motionEvent.rawX.toInt()
            pointerStartY = motionEvent.rawY.toInt()
            with(initialPosition()) {
                initialX = x
                initialY = y
            }
            moving = false
            longClickPerformed = false
            scheduleLongClickTimer()
        }

        MotionEvent.ACTION_MOVE -> {
            if (!longClickPerformed) {
                val deltaX = motionEvent.rawX - pointerStartX
                val deltaY = motionEvent.rawY - pointerStartY
                if (moving || hypot(deltaX, deltaY) > touchSlop) {
                    cancelLongClickTimer()
                    positionListener(initialX + deltaX.toInt(), initialY + deltaY.toInt())
                    moving = true
                }
            }
        }

        MotionEvent.ACTION_UP -> {
            cancelLongClickTimer()
            if (!moving && !longClickPerformed) {
                view.performClick()
            }
        }

    }

    return true
  }
  
}

We can add a bit of Kotlin magic - extension functions - to make it even easier to use:

fun View.registerDraggableTouchListener(  
  initialPosition: () -> Point,  
  positionListener: (x: Int, y: Int) -> Unit  
) {  
  DraggableTouchListener(context, this, initialPosition, positionListener)  
}

Theoretically, you can move anything with code above - the view serves here as a trigger only.

Let’s Get The Window Movin’

Everything we need to do is to attach our newly created DraggableTouchListener to the view we want to be used as a handle for moving the window.

For desktop operating systems, the window’s title bar typically serves this purpose, so let’s follow the same trend:

rootView.findViewById<View>(R.id.window_header).registerDraggableTouchListener(  
  initialPosition = { Point(windowParams.x, windowParams.y) },  
  positionListener = { x, y -> setPosition(x, y) }  
)

And that’s it!

Boundaries

It would be necessary for the productional use to implement some boundaries, so the window can’t be moved out of the screen too much.

In Floating Apps, I allow the user to move 50% of the window outside the screen area.

Results

Tap the window’s title bar and move it anywhere you want!

Source Code

The whole source code for this article is available on Github.

Stay Tuned

Eager to learn more about Android development? Follow me (@vaclavhodek) and Localazy (@localazy) on Twitter, or like Localazy on Facebook.

The Series

This article is part of the Floating Windows on Android series.

cmd line friendly app localization

Make sure you do not miss this update. Whether it is iOS or TypeScript app, you can localize your app using brand new Localazy CLI.

Read more