Public number: byte array, hope to help you 🤣🤣

There are so many mature solutions for Android screen adaptation that it’s no longer a hot topic. In my impression, 2018 was the hottest period of discussion on adaptation solutions. At that time, bytedance’s technical team published a paper introducing its adaptation solutions, and then many celebrities published articles to discuss it successively. At that time, I just work soon, have been asked in the interview questions about the screen adaptation, because for some understanding unclear concepts lead to answer not well 🤣 🤣 so this article wants to from beginning to end, clear about the screen adaptation of major knowledge, hope for your help, there is an error also hope readers can point to 🤣 🤣

I. PPI & DPI

There are two inextricable concepts about screen adaptation: PPI and DPI. Ppi and DPI are very similar in meaning and can be easily confused, but they belong to different fields

ppi

Pixels Per Inch (PPI) is the pixel density. It refers to the number of physical Pixels Per Inch. Ppi is the physical attribute value of the device, which depends on the screen itself, calculated by the formula shown below. Both dividend and divisor are objective and unchangeable values, so PPI is also unchangeable. It is an objective and unchangeable value on hardware

dpi

Dots Per Inch (DPI) was originally used in the printing industry to describe how many Dots are Per Inch, and in Android development to describe screen pixel density. Screen pixel density determines the total number of pixels per unit distance in the software concept. It is an attribute value that will be written into the system configuration file of the mobile phone when it leaves the factory. In general, users cannot modify this value, but there is an entry for modifying this value in the developer mode, and it is a value that can be modified in the software

As we know, the px value corresponding to 1 DP on different mobile phone screens may vary greatly. For example, 1 DP might correspond to 1 px on a small screen phone, and 3 px on a large screen phone, which is the basis of our screen adaptation. What determines how much px 1 dp corresponds to on a particular phone is the dPI value for that device, which can be measured by DisplayMetrics

val displayMetrics = applicationContext.resources.displayMetrics
Log.e("TAG"."densityDpi: " + displayMetrics.densityDpi)
Log.e("TAG"."density: " + displayMetrics.density)
Log.e("TAG"."widthPixels: " + displayMetrics.widthPixels)
Log.e("TAG"."heightPixels: " + displayMetrics.heightPixels)

TAG: densityDpi: 480
TAG: density: 3.0
TAG: widthPixels: 1080
TAG: heightPixels: 2259
Copy the code

Several things can be gleaned from this:

  1. The screen pixel density is 480 dpi
  2. Density equals 3, indicating that 1 DP equals 3 px on the device
  3. The screen width and height is 2259 x 1080 px, or 753 x 360 dp

The reference value of screen pixel density defined by Android system is 160 dpi, under which 1 dp equals 1 px, and so on, under 480 dpi, 1 dp equals 3 px. The calculation formula is as follows:

px = dp * (dpi / 160)
Copy the code

Devices with different screen pixel densities have different configuration qualifiers. For example, devices between 320 and 480 dpi are xxHDPI, which will preferentially fetch images from the drawable- xxhdPI folder

Two, why should adapt

No matter what unit we use in the layout file, eventually the system will need to convert it to PX for use, and since screen pixel sizes vary greatly from phone to phone, we naturally can’t hardcode px directly in the layout file. Therefore, Google also recommends developers to use DP as the unit value as possible, because the system will automatically complete the conversion between DP and PX according to the actual situation of the screen

Let me give you an example. Assuming the design is 1080 x 1920 px and 420 dpi, the width of the design is 411 x 731 dp, which is 205.5 DP for an ImageView that wants to take up half the screen width

So, for a 1440 x 2880 px, 560 dpi real machine with a width of 411 x 822 dp, we can use the width values in the layout file so that the ImageView takes up half of the screen width on this real machine. Although the screen pixels of the draft and the real machine are not the same, due to the pixel density of the screen, the DP width of the two is the same, allowing developers to use the same SET of DP size values to complete the design requirements

With DP, why do we need screen adaptation? Of course, dp only works in most normal cases. The above situation fits perfectly because the example is also perfect: 1440/1080 = 560/420 = 1.3333, the px width of the design and the real machine is exactly the same as the DP width, when dp is just right

Let’s do another example that’s not perfect. Take two real machines for example:

  • Huawei NOVa5:1080 x 2259 px, 480 dpi, screen width 1080 / (480/160) = 360 dp
  • Samsung Galaxy S10:1080 x 2137 px, 420 dpi, screen width 1080 / (420/160) = 411 dp

It can be seen that different phones may have different pixel densities with the same pixel width. It is possible that the phone manufacturers jointly determine the size of this value based on the pixels and size of the screen, but in any case, this results in a mismatch between the actual application and the design draft: For a 180 dp wide View, it takes up half the screen width on the huawei nova5, but only 180/411 = 0.43 on the Samsung Galaxy S10, which causes some deviation

The above situation is the direct use of DP value can not solve the problem, using DP can only adapt to most of the width-to-height ratio of conventional models, for special models are helpless……

Screen adaptation is to solve the above problems. For screen adaptation, there are two main things developers want to achieve:

  • In the declaration of width and height values, it is possible to directly apply the dimensions given on the design draft, which may be mapped to a specific value in the project or to multiple sets of values in dimens files, but either way, it is desirable to apply them directly in the development phase without having to manually calculate them. It’s about screen adaptation efficiency
  • The interface ends up with the same proportion of space on different screens. It’s all about the end result of screen adaptation

Here are three current or have been mainstream adaptation programs ~~

Toutiao program

Bytedance’s technology team published an article about its adaptation solution: a very low-cost adaptation for Android screens

Its adaptation idea is based on the following conversion formula:

  • px = density * dp
  • density = dpi / 160
  • px = dp * (dpi / 160)

Dp values declared in the layout file are eventually converted to PX using the applyDimension method of TypedValue. The conversion formula is density * dp

    public static float applyDimension(int unit, float value, DisplayMetrics metrics) {
        switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0 f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0 f/25.4 f);
        }
        return 0;
    }
Copy the code

If we can dynamically change the density value to a width equal to the width of the design draft, we can use the dp width and height values of the design draft directly in the layout file, and make the View occupy the same proportion on different phone screens.

For example, suppose a designer gives a design that is based on **1080 x 1920 px, density 2.625, 420 dpi **, and a width of 411 x 731 dp. So for a View with a width of 100 dp, the width ratio occupying the design draft is: 100 * 2.625/1080 = 0.2430

Take the data of the following two real computers as an example before adaptation:

  • Huawei Nova5:1080 x 2259 px, 480 dpi. Normally density is 3, and the View occupies a screen width ratio of 100 x 3/1080 = 0.2777
  • Pixel 2 XL: 1440 x 2800 px, 560 Dpi Normally the density is 3.5, and the View occupies a screen width ratio of 100 x 3.5/1440 = 0.2430

The solution of the Bytedance technical team was adopted to dynamically change the density for adaptation. The adaption density = the real width of the device (unit px)/the width of the design draft (unit DP) :

  • Huawei NOVA5: Density becomes 1080/411 = 2.6277, and View occupies a screen width ratio of 100 x 2.6277/1080 = 0.2433
  • Pixel 2 XL: Density becomes 1440/411 = 3.5036, and View occupies a screen width ratio of 100 x 3.5036/1440 = 0.2433

As you can see, there is a slight loss of precision due to division, but it is negligible. As long as we can dynamically change the density of the phone, the View will remain exactly the same in width as the design

Density is actually a public variable in the DisplayMetrics class. It does not involve any private API, and the modification theoretically does not affect the stability of the application. Therefore, once we modify density and densityDpi in the onCreate method of the Activity, we can use the dp value given by the design directly in the layout file, without having to prepare multiple sets of Dimens. It is very simple

    fun setCustomDensity(activity: Activity, application: Application, designWidthDp: Int) {
        val appDisplayMetrics = application.resources.displayMetrics
        val targetDensity = 1.0 f * appDisplayMetrics.widthPixels / designWidthDp
        val targetDensityDpi = (targetDensity * 160).toInt()
        appDisplayMetrics.density = targetDensity
        appDisplayMetrics.densityDpi = targetDensityDpi
        val activityDisplayMetrics = activity.resources.displayMetrics
        activityDisplayMetrics.density = targetDensity
        activityDisplayMetrics.densityDpi = targetDensityDpi
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        setCustomDensity(this, application, 420)
        super.onCreate(savedInstanceState)
    }
Copy the code

The bytedance technical team’s article only gives the sample code, not the code that will be used in the final implementation, but there is a well-known implementation library on GitHub that is worth checking out: AndroidAutoSize

Width and height qualifiers

The width and height qualifier is an adaptation that is natively supported by the system by enumerating the screen pixel sizes of all Android phones on the market. The idea is very simple, which is to generate a set of DIMens files for screens with different resolutions through scaling

First, using the size of the design as the base resolution, assuming that the design is 1920 x 1080 px, then the default dimens file can be generated using the following rules:

  • Divide the screen widths into 1080 pieces, each 1 px, and declare 1080 key values that start at 1 px and increase by 1 px each time
  • Divide the screen height into 1920 pieces, each 1 px high, and declare 1920 key values increasing by 1 px starting at 1 px

The final Dimens file looks like this:

<resources>
	<dimen name="x1">1px</dimen>
	<dimen name="x2">2px</dimen>...<dimen name="x1080">1080px</dimen>

	<dimen name="y1">1px</dimen>
	<dimen name="y2">2px</dimen>...<dimen name="y1920">1920px</dimen>
</resources>
Copy the code

Similarly, to generate a dedicated Dimens file for a phone with a screen size of 1440 x 720 px, generate the following rules:

  • Divide the screen widths into 1080 pieces, each 720/1080 = 0.666 px, and declare 1080 key values that start at 0.666 px and increase by 0.666 px each time
  • Divide the screen height into 1920 pieces, 1440/1920 = 0.75px, and declare 1920 key values that start at 0.75px and increase by 0.75px each time

The final Dimens file looks like this:

<resources>
	<dimen name="x1">0.666 px.</dimen>
	<dimen name="x2">1.332 px.</dimen>...<dimen name="x1080">720px</dimen>

	<dimen name="y1">0.75 px.</dimen>
	<dimen name="y2">1.5 px.</dimen>...<dimen name="y1920">1440px</dimen>
</resources>
Copy the code

Finally, a set of dedicated Dimens files will be generated for the mainstream screen sizes in the market according to the above rules, and each set of files will be placed in the value folder named after the pixel size, like the following:

values
values-1440x720
values-1920x1080
values-2400x1080
values-2408x1080
values-2560x1440
Copy the code

After that, we can develop directly with the pixel sizes in the design draft, which is 100 x 200 px, so we can refer to x100 and y200 directly in the layout file. When running on phones with different resolutions, the application will automatically reference dimens files with the same resolution, and the actual referenced PX values will have the same scale as the design draft, thus fulfilling the adaptation requirements

Note that the wide and high qualifier scheme has a fatal flaw: it requires a precise hit resolution to fit. For example, a 1920×1080 px phone must refer to dimens in the values-1920×1080 folder, otherwise it will have to refer to the default values folder, and the referenced size may be quite different from the actual requirements, resulting in a distorted interface. For the endless variety of resolutions available on the market, it is very difficult for developers to use them all, so the error tolerance rate of the wide and high qualifier scheme is very low

Fifth, smallestWidth

SmallestWidth is also an adaptation of the system’s native support. SmallestWidth is the minimum width, which refers to the shortest side length regardless of the screen direction. The adaptation principle is the same as the width and height qualifier scheme. In essence, a set of DIMens files are prepared for screens of different sizes through proportional conversion, and then the application references the matching DIMens files at run time. In order to achieve screen adaptation

First of all, we will use the size of the design as the base resolution. Assuming that the design is **1080 x 1920 pixels **, the base resolution will be the width of the design 1080 pixels

First generate dimens file for devices with width of 360 DP, and generate rules as follows:

  • Divide 360 DP into 1080 pieces, each 360/1080 DP, and declare 1080 key values. The value starts from 360/1080 DP and increases by 360/1080 DP each time

The final Dimens file looks like this:


      
<resources>
    <dimen name="DIMEN_1PX">0.33 dp</dimen>
    <dimen name="DIMEN_2PX">0.67 dp</dimen>...<dimen name="DIMEN_1078PX">359.33 dp</dimen>
    <dimen name="DIMEN_1079PX">359.67 dp</dimen>
    <dimen name="DIMEN_1080PX">360.00 dp</dimen>
</resources>
Copy the code

Similarly, we generate dimens files for devices with width 380 dp as described above:


      
<resources>
    <dimen name="DIMEN_1PX">0.35 dp</dimen>
    <dimen name="DIMEN_2PX">0.70 dp</dimen>...<dimen name="DIMEN_1078PX">379.30 dp</dimen>
    <dimen name="DIMEN_1079PX">379.65 dp</dimen>
    <dimen name="DIMEN_1080PX">380.00 dp</dimen>
</resources>
Copy the code

Finally, a set of dedicated Dimens files are generated for the main screen widths in the market according to the above rules, and each set of files is placed in a value folder named after the width, like the following:

values
values-sw360dp
values-sw380dp
values-sw400dp
values-sw420dp
Copy the code

This way, we can apply the px value of the design directly to the layout file, and the application will automatically match the resource file that best matches the current screen width at runtime. For example, if we refer to DIMEN_1080PX, the dp value corresponding to the reference takes up exactly the entire screen width in both 360 dp and 380 DP devices, thus fulfilling the fit requirement

The biggest difference between smallestWidth and width and height qualifier is fault tolerance. SmallestWidth has a high fault tolerance, even if no DIMens file matching the current screen width is found in the application. The application also looks down and takes the dimens file closest to the current screen width, and references the default dimens file only when none is found. As long as we prepare enough Dimens and each set of DIMens file increases in step by 5-10 DP, it can well meet the majority of mobile phones in the market. In addition, we can not only use the px width of the design as the base resolution, but also use the DP width instead, and the calculation rules remain the same

Six, summarized

The three schemes introduced above have their own characteristics, so here is a summary

  • Toutiao program. Advantages: Dp values in the design draft can be used directly without generating multiple sets of DIMens files for mapping. Therefore, apK volume will not be increased. In addition, the UI restore of this solution is probably the highest of the three solutions, the other two solutions require a precise hit to the screen size to achieve this solution’s restore. Disadvantages: Since this scheme affects the whole application, if we introduce some third party libraries, the interface in the third party library will also be affected, which may cause the effect to be distorted, requiring additional processing
  • Width and height qualifier scheme. The fault tolerance rate is too low, and it requires many sets of Dimens files, which was very popular when Android was just emerging and screen types were relatively few. There are probably few projects using this scheme now, so readers can ignore it
  • SmallestWidth scheme. Advantages: High fault tolerance, one dimens file every 10 dp between 320 and 460 dp is enough to use, if you want to include more devices, you can shorten the step size, there is no need to worry about the final effect will be too different from the design draft, and this solution will not affect the third party library. Disadvantages: Need to generate multiple sets of DIMens files, increase apK volume

It should be emphasized that there is a problem with all the above three schemes: we can only adapt to a single direction, and cannot take into account the width and height at the same time. The reason why there is only a single direction is that the aspect ratio of the current mobile phone screen is not increasing in accordance with a fixed ratio, 4:3, 16:9, or even other aspect ratios. In this context, it is unrealistic for us to achieve 100% restoration of the design draft, and we can only choose one dimension for adaptation. Fortunately, most of the time we only need to adapt according to the screen width, and this is enough for most of our development needs. For a small number of pages that need to be adapted according to the height, Toutiao program can be flexibly switched, smallestWidth program is more trouble, we can accurately control the width, height or position of ConstraintLayout by proportional control, also can meet the adaptation requirements

Also, I see a lot of developers on the web saying that DPI exists to make bigger phones display more content, and screen adaptation makes DPI lose its original purpose, but I don’t really understand what screen adaptation has to do with it. The current reality is that there are some mobile phones with the same screen pixel width, but their DPI is different. If dp is simply used without additional adaptation, the controls in such models will have more blank space or exceed the screen scope than the design draft, which is a problem that developers have to solve. If display more content refers to the control on the big screen mobile phone can occupy more physical space, so the premise is to make the size and location of the various controls are accord with the requirement of the design draft, screen adaptation have to do is that, under the same proportion control on the big screen mobile phone will have more physical space. And if showing more content means showing more controls when you have free space on a big screen phone than on a small screen, then I think not only developers are crazy, designers are crazy…

Finally, here is another code for generating dimens file, based on the smallestWidth scheme, the total code is less than 100 lines, the implementation idea is very clear in the previous article. Just fill in the width, height and pixel size of the draft. The default is 1080 x 1920 px. The generation range is from 320 to 460 dp, step size is 10 dp, and the reader can adjust as needed

If you need: SmallestWidthGenerator