The author

Hello everyone, my name is Big Saint;

I joined the 37 Mobile Game Android team in May 2018. I used to work for Internet companies such as Aipai.

Currently, I am the head of the Android team of 37 Mobile Games in China, mainly responsible for relevant business development and some daily business coordination.

background

As for me, I was too busy with my business recently, so I didn’t have time to think and settle something. At the same time, everyone in the group can have their own thinking and summary on the business. In such an atmosphere, I can’t help but drive to start writing something on the weekend, hoping that in addition to my daily busy business, I can precipitation something and add my own growth.

As for the entry point, recently, when I was doing the function of floating ball in the application, I needed to listen to the screen rotation event to adjust the position of the floating ball. I found that in some cases, I could not receive the system callback. After thinking about it, I did a simulated monitoring of the screen rotation, which basically achieved the goal.

The problem

After the hover ball stops dragging, it needs to be attached to the left and right sides of the phone screen.

In portrait mode, the x coordinate of 0 is the left edge, and the x coordinate of the screen width is the right edge.

But in landscape, the situation is more complicated. At present, most Android phones are designed with a fringe screen. In the full screen state, the floating ball cannot receive under the fringe when it is attached to the edge, otherwise it cannot be touched.

So you need to figure out the width of the fringe and use that as the starting point for the left side of the ball so that the ball doesn’t hide under the fringe. See the figure belowBut after the screen rotates, the bangs go to the right, and the left should not be the width of the bangs as the starting point for the levitating ball. In this case, you need to listen to the rotation of the screen, with the Angle of the screen direction, you can correctly judge. Listening to the rotation of the screen simply requires rewriting the Activity’s onConfiguratuonChanged lifecycle.

override fun onConfigurationChanged(newConfig: Configuration) {
 super.onConfigurationChanged(newConfig)
 Log.i(TAG, "on configuration changed")}Copy the code

It’s configured in the AndroidManifest

android:configChanges="orientation|screenSize"
Copy the code

When setting the Activity’s screenOrientation to sensorLandscape, the callback is not received even when the screen is rotated. Set screenOrientation to a sensor so that the screen rotates back to this position. After several attempts, you can only get the callback when switching between landscape and portrait. If you flip the landscape screen upside down, you won’t get the callback even if the landscape state stays the same and the direction is reversed.

solution

Since onConfigurationChanged does not receive callbacks, another option is to listen for screen orientation degrees

mOrientationEventListener = object : OrientationEventListener(this) {
 override fun onOrientationChanged(orientation: Int) {
 Log.i(TAG, "on orientation changed angle is $orientation")
 if (orientation > 340 || orientation < 20) {
 / / 0
 } else if (orientation in 71.109.) {
 / / 90
 } else if (orientation in 161.199.) {
 / / 180
 } else if (orientation in 251.289.) {
 / / 270}}}Copy the code

Determine whether the bangs are on the left or right by the degree, that is, 270 degrees on the left and 90 degrees on the right. This approach seems to solve the problem, but after a few more rotations, it turns out that there are other problems. In normal thinking, the direction of the screen should be the same as this degree, that is, the display of the screen should be top-down. But not the picture below.

At this time, the degree is 90, but the screen is displayed upside down and not rotated into the upright state. However, according to the above code, 90 degrees will be judged as the normal state of 90 degrees upright display. At this time, it is wrong to modify the position of the floating ball.

If you receive the onOrientationChanged callback to determine the direction of the screen, you can determine the direction of the screen when the degree reaches 90 degrees. If both conditions are met, the screen is rotated.

Use the following code to determine the screen display direction

val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as
WindowManager
val rotation = windowManager.defaultDisplay?.rotation
The rotation to 0, 1, 2, and 3 is the four directions of the screen
Copy the code

The onOrientationChanged callback is very sensitive. If the screen is moved a little, the callback will be returned. I would like to simulate a normal screen rotation event to modify the position of the floating ball, so it can’t be refreshed very often. Here do the control, all the code is as follows:

object ScreenOrientationHelper {
    val ORIENTATION_TYPE_0 = 0
    val ORIENTATION_TYPE_90 = 90
    val ORIENTATION_TYPE_180 = 180
    val ORIENTATION_TYPE_270 = 270
    private var mOrientationEventListener: OrientationEventListener? = null
    private var mScreenOrientationChangeListener:
            ScreenOrientationChangeListener? = null
    private var currentType = ORIENTATION_TYPE_0

    fun init(context: Context, listener: ScreenOrientationChangeListener) {
        mScreenOrientationChangeListener = listener
        mOrientationEventListener = object :
                OrientationEventListener(context) {
            override fun onOrientationChanged(orientation: Int) {
                if (mScreenOrientationChangeListener == null) {
                    return
                }
                if (orientation > 340 || orientation < 20) {
                    / / 0
                    if (currentType == 0) {
                        return
                    }
                    if(getScreenRotation(context) == Surface.ROTATION_0) { mScreenOrientationChangeListener!! .onChange(ORIENTATION_TYPE_0) currentType = ORIENTATION_TYPE_0 } }else if (orientation in 71.109.) {
                    / / 90
                    if (currentType == 90) {
                        return
                    }
                    val angle = getScreenRotation(context)
                    if(angle == Surface.ROTATION_270) { mScreenOrientationChangeListener!! .onChange(ORIENTATION_TYPE_90) currentType = ORIENTATION_TYPE_90 } }else if (orientation in 161.199.) {
                    / / 180
                    if (currentType == 180) {
                        return
                    }
                    val angle = getScreenRotation(context)
                    if(angle == Surface.ROTATION_180) { mScreenOrientationChangeListener!! .onChange(ORIENTATION_TYPE_180) currentType = ORIENTATION_TYPE_180 } }else if (orientation in 251.289.) {
                    / / 270
                    if (currentType == 270) {
                        return
                    }
                    val angle = getScreenRotation(context)
                    if(angle == Surface.ROTATION_90) { mScreenOrientationChangeListener!! .onChange(ORIENTATION_TYPE_270) currentType = ORIENTATION_TYPE_270 } } } } register() }private fun getScreenRotation(context: Context): Int {
        val windowManager =
                context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        return windowManager.defaultDisplay?.rotation ?: 0
    }

    fun register(a) {
        if(mOrientationEventListener ! =null) { mOrientationEventListener!! .enable() } }fun unRegister(a) {
        if(mOrientationEventListener ! =null) { mOrientationEventListener!! .disable() } }interface ScreenOrientationChangeListener {
        / * * * *@param orientation
         */
        fun onChange(orientation: Int)}}Copy the code

To use, go straight like this:

ScreenOrientationHelper.init(this.object :
ScreenOrientationHelper.ScreenOrientationChangeListener {
   override fun onChange(orientation: Int) {
       when(orientation) {
         ScreenOrientationHelper.ORIENTATION_TYPE_0 -> {}
         ScreenOrientationHelper.ORIENTATION_TYPE_90 -> {}
         ScreenOrientationHelper.ORIENTATION_TYPE_180 -> {}
         ScreenOrientationHelper.ORIENTATION_TYPE_270 -> {}
       }
   }
})
Copy the code

If the onOrientationChanged callback is within 90 degrees, the screen is compared to surface.rotation_270, and if the onOrientationChanged callback is within 270 degrees, the screen is compared to surface.rotation_90. You can see that the Angle is increasing clockwise, and the screen direction is counting degrees counterclockwise.

Other problems

There was another problem with the method in the test. While onOrientationChanged was a sensitive callback, it also changed the degree of the screen and changed the direction of the screen. That is, it kept the screen in the same direction but increased the slope of the screen (hold the phone against the desktop and slowly stand up). When the gradient reaches a certain point, the screen will rotate and onOrientationChanged will not be called back because there is no change. This will not allow you to receive the screen rotation callback, but in the actual mobile phone scenario, this situation is relatively rare, you can try it yourself.

summary

In normal development, it is necessary to distinguish which state landscape scene is less, otherwise I think Android will give an accurate callback. Android devices are highly fragmented. In addition to bangs, there is a virtual navigation bar at the bottom edge of the screen, and the status of this navigation bar is different under different system Settings. So at this time in the suspension ball welt this demand not only to consider bangs, but also to consider the navigation bar. What’s more, the virtual navigation stays in one direction during rotation, superimposed on bangs. So the first step in figuring out the position is to listen for the rotation of the screen.