The project address

Toutiao screen adaptation scheme & vertical screen switching optimization

Begin to use

  • Add the following dependencies tomoduleUnder thebuild.gradleIn the

dependencies {

    implementation 'com. Oikawaii. Library: core: 1.0.0'

    implementation 'com. Oikawaii. Library: density: 1.0.0'

}

Copy the code

Method of use

  • inApplication(note that in thesuper.onCreate()The location of the)

package ${PACKAGE_NAME}

import android.app.Application

import com.oikawaii.library.core.android.app

import com.oikawaii.library.density.DensityUtil

class ${NAME} : Application() {

    override fun onCreate(a) {

        app = this

        DensityUtil.init()

        super.onCreate()

    }

}

Copy the code
  • inActivity(note that in thesuper.onCreate()The location of the)

class ${NAME} : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {

        DensityUtil.init(this.420f, status = false, navigation = false, flag = Density.SHORT_SIDE_BASED)

        super.onCreate(savedInstanceState)

        }

    }

}

Copy the code

preface

As for the advantages and disadvantages of Toutiao’s screen adaptation scheme, the content of relevant articles has been relatively mature, so I will not go into details here. This is mainly an issue to solve the horizontal and vertical screen switching in the comment of the paper on the landing study of Toutiao’s screen adaptation scheme.

Related articles:

An extremely low-cost Android screen adaptation (Bytedance Technical Team)

Research on Toutiao Screen Adaptation Scheme (codeGoogle)

About vertical and horizontal switching

Somehow the screen of the Android developer is the relative is not too care about problems, because the ecological terms of the Android tablet itself is not very successful, mobile phone can also use Android: screenOrientation = “portrait” to ban the use of landscape pattern, You can also use the layout-land resource folder.

In toutiao adaptation solution brings an advantage — “known width”, to fix the design width of 420DP as an example, if you want a Win10 Logo in the middle of the screen (virtual), just need to do so:


<?xml version="1.0" encoding="utf-8"? >

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_gravity="center"

    android:layout_height="420dp" >

    <TextView

        android:background="#3CB6F8"

        android:layout_width="420dp"

        android:layout_height="420dp" />

    <TextView

        android:layout_alignParentStart="true"

        android:layout_alignParentLeft="true"

        android:layout_alignParentTop="true"

        android:background="#FF554C"

        android:layout_width="210dp"

        android:layout_height="210dp" />

    <TextView

        android:layout_alignParentBottom="true"

        android:layout_alignParentRight="true"

        android:layout_alignParentEnd="true"

        android:background="#72D44F"

        android:layout_width="210dp"

        android:layout_height="210dp" />

</RelativeLayout>

Copy the code

But now the landscape looks like this:

At this time, there are two problems in the landscape screen: incomplete square display and square dislocation.

Solve horizontal screen dislocation

The reason for the misplacement of landscape is simple, but before you fix it, take a look at how it works on Android 10.

So the solution is very simple to upgrade Android 10, so the reason is very simple virtual keys.

The solution is simple:

Method 1: adding the decision of the navigation Bar is enough. In order to avoid the need to add the decision of the Status Bar at the same time.


/** * Initializes Density in the Activity@paramThe design width of the ruler UI *@paramStatus Specifies whether to include the title bar * when setting the base size@paramNavigation specifies whether to include the virtual key */ when setting the baseline dimensions

fun init(activity: Activity, ruler: Float, status: Boolean, navigation: Boolean)

    val width = DimenUtil.width(navigation)

    val height = DimenUtil.height(status, navigation)

    val density = width / ruler

    val res = activity.resources

    val metrics = res.displayMetrics

    metrics.density = density

    metrics.densityDpi = (160 * density).toInt()

    metrics.scaledDensity = density * (scaledDensity / this.density)

    scale = this.density / density

    setBitmapDensity(metrics.densityDpi)

}

Copy the code

Method 2: Check automatically according to SystemUiVisibility.


/** * Initializes Density in the Activity@paramDesign width of ruler UI */

fun init(activity: Activity, ruler: Float) {

    val f = activity.window.decorView.systemUiVisibility

    val sf1 = View.SYSTEM_UI_FLAG_FULLSCREEN

    val sf2 = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

    val nf = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

    val sf = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    val navigation =  ((f and sf) == sf) || ((f and nf) == nf)

    val status = ((f and sf) == sf) || ((f and sf1) == sf1) || ((f and sf2) == sf2)

    val width = DimenUtil.width(navigation)

    val height = DimenUtil.height(status, navigation)

    val density = width / ruler



    val res = activity.resources

    val metrics = res.displayMetrics

    metrics.density = density

    metrics.densityDpi = (160 * density).toInt()

    metrics.scaledDensity = density * (scaledDensity / this.density)

    scale = this.density / density

    setBitmapDensity(metrics.densityDpi)

}

Copy the code

(Refer to Android common utility class (based on Kotlin) and relevant source code for calculation and pit removal of Navigation Bar and Status Bar.)

Resolve incomplete square display

As for the incomplete display of the square, in fact, there is a foreshadowing in front of it — “known width”. It is this advantage that brings about the display problem of the landscape screen. The layout file only uses a specific DP value to set the size, and this DP value is based on the design width.

The solution, too, is simple:

Method 1: set the base edge (e.g., width based, height based, short based, long based), set the design size for the base edge.


import androidx.annotation.IntDef;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

@IntDef({ Density.WIDTH_BASED, Density.HEIGHT_BASED, Density.LONG_SIDE_BASED, Density.SHORT_SIDE_BASED })

@Retention(RetentionPolicy.SOURCE)

public @interface Density {

    int WIDTH_BASED = 1;

    int HEIGHT_BASED = 2;

    int LONG_SIDE_BASED = 3;

    int SHORT_SIDE_BASED = 4;

}

Copy the code

Note the height of the Status Bar when using height as the base edge.


/** * Initializes Density in the Activity@paramThe design width of the ruler UI *@paramStatus Specifies whether to include the title bar * when setting the base size@paramNavigation specifies whether to include the virtual key */ when setting the baseline dimensions

fun init(activity: Activity, ruler: Float, status: Boolean, navigation: Boolean.@Density flag: Int) {

    val width = DimenUtil.width(navigation)

    val height = DimenUtil.height(status, navigation)

    val pixels = when(flag) {

        Density.WIDTH_BASED -> width

        Density.HEIGHT_BASED -> height

        Density.SHORT_SIDE_BASED -> if(app.isPortrait) width else height

        Density.LONG_SIDE_BASED -> if(app.isLandscape) width else height

        else -> 0

    }

    val density = pixels / ruler

    val res = activity.resources

    val metrics = res.displayMetrics

    metrics.density = density

    metrics.densityDpi = (160 * density).toInt()

    metrics.scaledDensity = density * (scaledDensity / this.density)

    scale = this.density / density

    setBitmapDensity(metrics.densityDpi)

}

Copy the code

(For the Status judgment of Navigation Bar and Status Bar, refer to common Android tool classes (based on Kotlin) and relevant source code.)

Method 2: With width as the reference edge, set different design widths for landscape screen and portrait screen. (Since the Status Bar is independent of width, you can remove the size of the Status Bar)


/** * Initializes Density in the Activity@paramPortRuler UI design width when portrait *@paramDesign width of landRuler UI in landscape *@paramNavigation specifies whether to include the virtual key */ when setting the baseline dimensions

fun init(activity: Activity, portRuler: Float, landRuler: Float, navigation: Boolean) {

    val width = DimenUtil.width(navigation)

  val ruler = if(app.isLandscape) landRuler else portRuler

    val density = width / ruler

    val res = activity.resources

    val metrics = res.displayMetrics

    metrics.density = density

    metrics.densityDpi = (160 * density).toInt()

    metrics.scaledDensity = density * (scaledDensity / this.density)

    scale = this.density / density

    setBitmapDensity(metrics.densityDpi)

}

Copy the code

Appendix — Core code

(See Android common utility classes (based on Kotlin) for the implementation of some methods.)

  • DensityUtil.kt

import android.view.View

import android.app.Activity

import android.util.DisplayMetrics

import android.content.res.Configuration

import android.content.ComponentCallbacks

import com.oikawaii.library.core.android.app

import com.oikawaii.library.core.android.util.DimenUtil

import com.oikawaii.library.core.android.ktx.isPortrait

import com.oikawaii.library.core.android.ktx.isLandscape

/ * * *@author Sukcria Miksria

* @version2018/10/01. * * /

object DensityUtil : ComponentCallbacks {

    var scale = 0f                                  // Screen zoom

    private var density: Float = 0f                / / Application DisplayMetrics

    private var scaledDensity: Float = 0f          //

    private lateinit var metrics: DisplayMetrics    / / Application of Density

    /** * Initializes Metrics in Application */

    fun init(a) {

        // Get application DisplayMetrics

        metrics = app.resources.displayMetrics

        // Determine whether initialization is required

        if(density ! =0f) return

        / / initialization

        density = metrics.density

        scaledDensity = metrics.scaledDensity

        // Listen for font changes

        app.registerComponentCallbacks(this)}/** * Initializes Density in the Activity@paramThe design width of the ruler UI is 420dp */ by default

    fun init(activity: Activity, ruler: Float = 420f, @Density flag: Int = Density.SHORT_SIDE_BASED) {

        val f = activity.window.decorView.systemUiVisibility

        val sf1 = View.SYSTEM_UI_FLAG_FULLSCREEN

        val sf2 = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

        val nf = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

        val sf = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

        val navigation =  ((f and sf) == sf) || ((f and nf) == nf)

        val status = ((f and sf) == sf) || ((f and sf1) == sf1) || ((f and sf2) == sf2)

        init(activity, ruler, status, navigation, flag)

    }

    /** * Initializes Density in the Activity@paramThe design width of the ruler UI is 420dp * by default@paramStatus Specifies whether to include the title bar * when setting the base size@paramNavigation specifies whether to include the virtual key */ when setting the baseline dimensions

    fun init(activity: Activity, ruler: Float = 420f, status: Boolean = false, navigation: Boolean = false.@Density flag: Int = Density.SHORT_SIDE_BASED) {

        val width = DimenUtil.width(navigation)

        val height = DimenUtil.height(status, navigation)

        val pixels = when(flag) {

            Density.WIDTH_BASED -> width

            Density.HEIGHT_BASED -> height

            Density.SHORT_SIDE_BASED -> if(app.isPortrait) width else height

            Density.LONG_SIDE_BASED -> if(app.isLandscape) width else height

            else -> 0

        }

        init(activity.resources.displayMetrics, pixels / ruler)

    }

    /** * Initializes Density in the Activity@paramPortRuler UI design width when portrait, default is 420dp *@paramThe design width of landRuler UI in landscape. Default is 980dp */

    fun init(activity: Activity, portRuler: Float = 420f, landRuler: Float) {

        val f = activity.window.decorView.systemUiVisibility

        val nf = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

        val sf = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

        val navigation =  ((f and sf) == sf) || ((f and nf) == nf)

        init(activity, portRuler, landRuler, navigation)

    }

    /** * Initializes Density in the Activity@paramPortRuler UI design width when portrait, default is 420dp *@paramThe design width of landRuler UI in landscape. Default is 980dp *@paramNavigation specifies whether to include the virtual key */ when setting the baseline dimensions

    fun init(activity: Activity, portRuler: Float = 420f, landRuler: Float, navigation: Boolean = false) {

        val pixels = DimenUtil.width(navigation)

        val ruler = if(app.isLandscape) landRuler else portRuler

        init(activity.resources.displayMetrics, pixels / ruler)

    }

    /** * Initializes the Activity's Density */

    private fun init(metrics: DisplayMetrics, density: Float) {

        metrics.density = density

        metrics.densityDpi = (160 * density).toInt()

        metrics.scaledDensity = density * (scaledDensity / this.density)

        scale = this.density / density

        setBitmapDensity(metrics.densityDpi)

    }

    /** * Set the Bitmap's default screen density * Since the Bitmap's screen density is configured to read, it needs to be forcibly changed using reflection *@paramDensity Screen density */

    private fun setBitmapDensity(density: Int) {

        try {

            val cls = Class.forName("android.graphics.Bitmap")

            val field = cls.getDeclaredField("sDefaultDensity")

            field.isAccessible = true

            field.set(null, density)

            field.isAccessible = false

        } catch (e: ClassNotFoundException) {

        } catch (e: NoSuchFieldException) {

        } catch (e: IllegalAccessException) {

            e.printStackTrace()

        }

    }

    /** * callback for font changes */

    override fun onLowMemory(a) {}

    /** * callback for font changes */

    override fun onConfigurationChanged(newConfig: Configuration) {

        // Reassign appScaledDensity after the font change

        if (newConfig.fontScale > 0) scaledDensity = metrics.scaledDensity

    }

}

Copy the code
  • Density.java

import androidx.annotation.IntDef;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

/ * * *@author Sukcria Miksria

* @version2019/10/04 * /

@IntDef({ Density.WIDTH_BASED, Density.HEIGHT_BASED, Density.LONG_SIDE_BASED, Density.SHORT_SIDE_BASED })

@Retention(RetentionPolicy.SOURCE)

public @interface Density {

    int WIDTH_BASED = 1;

    int HEIGHT_BASED = 2;

    int LONG_SIDE_BASED = 3;

    int SHORT_SIDE_BASED = 4;

}

Copy the code