preface

In Android development, due to the fragmentation of Android and the wide variety of screen resolutions, it is increasingly expensive to adapt to a more or less uniform display across all resolutions. Android officially offers DP units for adaptation, but they don’t perform very well in all sorts of weird resolutions, so let’s explore a simple and less intrusive adaptation. This article will recommend two screen adaptation solutions that you can use as required.

Disadvantages of traditional DP adaptation

Dp in Android will be converted to PX before rendering.

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

The DPI is calculated based on the actual screen resolution and size, which may vary from device to device.

The relationship between screen size, resolution and pixel density

Under normal circumstances, the resolution of a mobile phone is wide x high, and the screen size is in inches, so the relationship between the three is:

For example, if the screen resolution is 1920 x 1080 and the screen size is 5 inches, the DPI is 440.

What’s the problem with that?

Assuming that our UI design is designed with a screen width of 360DP, the screen width on the above device is actually 1080/(440/160)=392.7 DP, which means that the screen is wider than the design. In this case, even using DP will not display the same effect on different devices. At the same time, the screen width of some devices is less than 360DP, which will lead to the actual display of incomplete development based on 360DP width.

Moreover, the above relationship between screen size, resolution and pixel density is not realized by many devices according to this rule, so the VALUE of DPI is very disorderly and there is no rule to follow, resulting in unsatisfactory adaptation effect of DP.

Toutiao lightweight adaptation scheme

Comb demand

First of all, let’s sort out our requirements. Generally, our design drawings are designed with fixed sizes. For example, if the screen is designed with a resolution of 1920px by 1080px and density is 3, the screen is actually 640DP by 360DP. If we want to display all the same on all the devices, it is not practical because the aspect ratio of the screen is not fixed, 16:9, 4:3, and other aspect ratios constantly appear, so it is impossible to display all the same. But usually, we only need a dimension to width or height to fit, for example, we Feed is sliding up and down, just make sure according to eye to eye on all of the dimensions of equipment in the wide, such as another does not support the sliding up and down the page, you need to ensure they show in the high dimension, especially not shown on some equipment is not complete. At the same time, considering that the current adaptation is based on DP, if the new scheme does not support DP, the cost of migration will be very high.

Therefore, the general requirements are summarized as follows:

  • The width or height of a dimension is compatible with the design drawing.
  • Support dp and SP units to minimize migration costs.

Find a compatible breakthrough

Px = dp * density

As you can see, if the design is 360dp wide, we have to change the density value to ensure that the px values calculated on all devices are exactly the screen width.

Density is a member variable of DisplayMetrics, and DisplayMetrics instances are available via Resources#getDisplayMetrics, Resouces are obtained from the Activity or Application Context.

Let’s get familiar with a few variables related to DisplayMetrics neutralization and adaptation:

  • DisplayMetrics#density is the density described above

  • DisplayMetrics#densityDpi is the dpi above

  • DisplayMetrics#scaledDensity specifies the scale factor of a font. Normally, it is the same as density, but adjusting the system font size will change this value

So are all dp and PX conversions calculated using the relevant values in DisplayMetrics?

TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics)


/**
     * Converts an unpacked complex data value holding a dimension to its final floating 
     * point value. The two parameters <var>unit</var> and <var>value</var>
     * are as in {@link #TYPE_DIMENSION}.
     *  
     * @param unit The unit to convert from.
     * @param value The value to apply the unit to.
     * @param metrics Current display metrics to use in the conversion -- 
     *                supplies display density and scaling information.
     * 
     * @return The complex floating point value multiplied by the appropriate 
     * metrics depending on its unit. 
     */
    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:
            returnValue * metrics. Xdpi * (1.0f/72);case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            returnValue * metrics. Xdpi * (1.0f/25.4f); }return 0;
    }

Copy the code

And the DisplayMetrics that we’re using here is actually from Resources.

Decode BitmapFactory#decodeResourceStream

/**
     * Decode a new Bitmap from an InputStream. This InputStream was obtained from
     * resources, which we pass to be able to scale the bitmap accordingly.
     * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig}
     *         is {@link android.graphics.Bitmap.Config#HARDWARE}
     *         and {@link BitmapFactory.Options#inMutable} is set, if the specified color space
     *         is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
     *         function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
     */
    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) {
        validate(opts);
        if (opts == null) {
            opts = new Options();
        }

        if(opts.inDensity == 0 && value ! = null) { final int density = value.density;if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density;
            }
        }
        
        if(opts.inTargetDensity == 0 && res ! = null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; }return decodeStream(is, pad, opts);
    }
Copy the code

Visibility is also calculated using values in DisplayMetrics.

Of course, there are other DP transformation scenarios, which are basically calculated by DisplayMetrics, and I won’t go into detail here. Therefore, to meet the above requirements, we only need to modify the variables related to DisplayMetrics and DP transformation.

Final plan

Let’s assume that the width of the design is 360DP, and the wide dimension fits.

Then the adaptable density = the real width of the device (unit: px) / 360, then we just need to modify the calculated density in the system, the code is as follows:

/**
     * 
     * @param activity
     * @param application
     */
    private void setCustomDensity(@NonNull Activity activity, @NonNull Application application) { //application final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics(); // The width is 360DP and the height is 640DP final according to the actual situationfloat targetDensity = appDisplayMetrics.widthPixels / 360;
        final int targetDensityDpi = (int) (targetDensity * 160);

        appDisplayMetrics.density = appDisplayMetrics.scaledDensity = targetDensity;
        appDisplayMetrics.densityDpi = targetDensityDpi;

        //activity
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();

        activityDisplayMetrics.density = appDisplayMetrics.scaledDensity = targetDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }
Copy the code

Also called in the Activity#onCreate method. The code is relatively simple and does not involve the invocation of the system’s non-public API, so theoretically it will not affect the stability of APP.

After the modification, a version of gray scale was tested online, and the stability met expectations. There was no crash caused by the modification, but a lot of feedback about the font being too small was received:

final float targetScaledDensity = targetDensity * (appDisplayMetrics.scaledDensity / appDisplayMetrics.density);
Copy the code

However, another problem was found after testing. If you switch fonts in system Settings and then return to the application, the fonts did not change. So still have to monitor the font switch, call application # registerComponentCallbacks registered onConfigurationChanged under surveillance.

private static float sRoncompatDennsity;
    private static float sRoncompatScaledDensity;

    private void setCustomDensity(@NonNull Activity activity, final @NonNull Application application) {

        //application
        final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();

        if (sRoncompatDennsity == 0) {
            sRoncompatDennsity = appDisplayMetrics.density;
            sRoncompatScaledDensity = appDisplayMetrics.scaledDensity;
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(Configuration newConfig) {
                    if(newConfig ! = null && newConfig.fontScale > 0) { sRoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity; } } @Override public voidonLowMemory() {}}); } // If the width is 360DP, the height can be set to 640dpfloat targetDensity = appDisplayMetrics.widthPixels / 360;
        final float targetScaledDensity = targetDensity * (sRoncompatScaledDensity / sRoncompatDennsity);
        final int targetDensityDpi = (int) (targetDensity * 160);

        appDisplayMetrics.density = targetDensity;
        appDisplayMetrics.densityDpi = targetDensityDpi;
        appDisplayMetrics.scaledDensity = targetScaledDensity;

        //activity
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();

        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;
        activityDisplayMetrics.scaledDensity = targetScaledDensity;
    }
Copy the code

Comparison after adaptation

Another option is AndroidScreenAdaptation.

AndroidScreenAdaptation gihub address

This Kurt point

You don’t have to change your layout writing habits at all, just write the layout the way you wrote it. There is no need to inherit the adaptation class, there is no need to adapt the layout in the outermost package, there is no need to create a vast number of resolution adaptation folders, there is no need to force the use of PX as a unit, support code dynamically add view adaptation, you can preview the layout in real time, meet the rotation and split screen adaptation, full screen or mobile phone adaptation with virtual keys is no problem.

Results show

Quick start

Add the dependent

Implementation ‘me. Yatoooon: screenadaptation: 1.1.1’

Initial chemical tools

(1) Create your own Application inheriting application

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ScreenAdapterTools.init(this);
    }
Copy the code

Note: Rotation fit, if the application screen is fixed in a certain direction does not rotate (such as QQ and wechat), the following is not written.

@Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        ScreenAdapterTools.getInstance().reset(this);
    }
Copy the code

(2) Declare to use your own application in androidmanifest.xml and add meta-data. The examples indicate the meaning of these data

<application
        android:name=".App". <meta-data android:name="designwidth"
            android:value="1080"<meta-data Android :name= <meta-data Android :name= <meta-data Android :name="designdpi"
            android:value="480"<meta-data android:name= "<meta-data android:name=" <meta-data Android :name= "<meta-data Android :name="fontsize"
            android:value="1.0"<meta-data android:name= <meta-data android:name= <meta-data android:name= <meta-data android:name="unit"
            android:value="px"</application> </application>Copy the code

Width 240 320 480 720 1080 1440 DPI level LDPI MDPI HDPI XHDPI XXHDPI XXXHDPI DPI Value 120 160 240 320 480 640

Begin to use

(1.) In the Activity, find setContentView (R.layout.xxxxxx)

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_dp); //ScreenAdapterTools.getInstance().reset(this); // if you want split screen on android7.0, add this sentence // insetContentView(); Followed by adaptation statement ScreenAdapterTools. GetInstance () loadView (getWindow (). GetDecorView ()); }}Copy the code

(2) In Fragment or recyclerView, ListView or GridView, viewPager, custom view, etc., as long as you can find the layout filler (custom view is completely code drawn with no layout filler how to do? Look down)

public class TestFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.test_px, container, false); / / get the layout filler ScreenAdapterTools. After returning to the view of the getInstance (). The loadView (view);returnview; }}Copy the code

Note: 1. The custom view, in ScreenAdapterTools. GetInstance (). The loadView (view); Wrap a layer around it, otherwise you will have a problem previewing the XML when writing a layout file with a custom view! But does not affect the real machine operation effect.

        if(! isInEditMode()) { ScreenAdapterTools.getInstance().loadView(view); }Copy the code

2. What about custom views drawn completely in code? Let’s say I draw a circle with a radius of 100dp and find the place in the code to get the radius property value circleRadius

       circleRadius = ScreenAdapterTools.getInstance().loadCustomAttrValue(circleRadius);
Copy the code

(3.) now open your layout file, and open the preview, click the preview of the upper small cell phone icon selection and matching your design simulator, then you can according to the design measure and write layout file, and write unit measured in px or dp depends on the value of the unit to fill in in meta_data in your manifest file, temporary support only high wide MinmaxWidthHeight = minmaxWidthHeight = minmaxWidthHeight loadView(… View,new CustomConversion()), if you need any other property values, please write your own IConversion and AbsLoadViewHelper), after the layout file is complete, you see what the preview is like, the various real machine is running.

The principle of

I do not want to mention those long articles, presumably readers have been reading crazy elsewhere, just know a few simple concepts to use

  • Px is a unit of resolution such as 1080 by 1920 on mainstream mobile phones.
  • Dp is developed exclusively for Android units under different phones 1DP = different PX.
  • Sp is the font size (dp or PX is also required in the previous listing file). Sp changes with the font size of the system, but according to my observation, the font of apps like wechat and QQ does not change with the font size displayed by the system.

This library is according to the design value of the width of the unit (px) and the corresponding standards of dpi to adaptation (phone actual width is relative to the design to increase or decrease, in proportion to height (which is the ratio of width to increase or decrease) the proportion of the increase or decrease), all the layout of the control according to the proportion (mobile actual width/design width) to fit in different resolution, different Ppi (mobile screen density, also known as DPI), different minimum widths (some people like to adjust the minimum width under the developer option, mainstream phones default to 360DP) for the phone.

This article is not original and is recommended.