Dp in Android will be converted to PX before rendering.

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

Generally, our design drawings are designed with fixed dimensions. 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 show the same on all devices, it is not realistic, because the screen aspect ratio is not constant, 16:9 ratio, width to height ratio 4:3 even other emerge in endlessly, aspect ratio is different, according to the same can not be, even if the same resolution of different manufacturers have different density of the phone’s screen, we need to do it.

To do screen adaptation, let’s first understand a formula

The formula for converting from DP to PX:

  • px = dp * density

As you can see, if the design is 360dp wide and you want to make sure that the px values calculated on all devices are exactly the width of the screen, you can change the density value to achieve this effect. Density is the member variables in DisplayMetrics, and DisplayMetrics instance through Resources. GetDisplayMetrics can get, Resouces are obtained from the Activity or Application Context.

DisplayMetrics neutralization and adaptation related variables:

  • DisplayMetrics. Density is the density described above

  • DisplayMetrics. DensityDpi is the dpi

  • DisplayMetrics scaledDensity font scaling factors, under normal circumstances and density are equal, but after regulating system font size will change this value

We know that no matter what unit we set the system will eventually convert to PX to calculate the conversion code of the system, okay

  • TypedValue. ApplyDimension (int unit, float value, DisplayMetrics metrics) to convert:
    public static float applyDimension(int unit, floatThe value of 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

Pictures of decode, BitmapFactory decodeResourceStream method

	    @Nullable
    public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
            @Nullable InputStream is, @Nullable Rect pad, @Nullable 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;
            }
        }
        
	// densityDpi is used here
        if (opts.inTargetDensity == 0&& res ! =null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }
        
        return decodeStream(is, pad, opts);
    }
Copy the code

If we design a 360dp screen by default, we should first set the view width to half the screen width of 180dp, which should be 540px on a 1080 x 1920 screen. By calculating

  • Density = 1080/360; desity = 3

According to TypedVaule. ApplyDimens, the conversion is 180dp * 3 = 540px. If the screen is 720 * 1280, half of the screen width is 360px

  • Density = 720/360, density = 2;

According to TypedVaule. ApplyDimens, the conversion is 180dp * 2 = 360px

Therefore, our final implementation scheme is as follows:

    private static final float defaultWidth = 360;
    private static float appDensity;
    private static float appScaleDensity;

    public static void setCustomDensity(Application application, Activity activity){
        DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
        if (appDensity == 0){
            appDensity = displayMetrics.density;
            appScaleDensity = displayMetrics.scaledDensity;
	        // Set the listener after modifying the system font
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(@NonNull Configuration newConfig) {
                    if(newConfig ! =null && newConfig.fontScale >0){ appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity; }}@Override
                public void onLowMemory(a) {}}); }final float targetDensity = displayMetrics.widthPixels/defaultWidth;
        final float targetScaleDensity = targetDensity *(appScaleDensity/appDensity);
        final int  targetDensityDpi = (int) (targetDensity * 160);
        displayMetrics.density = targetDensity;
        displayMetrics.scaledDensity = targetScaleDensity;
        displayMetrics.densityDpi = targetDensityDpi;
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.scaledDensity = targetScaleDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }
Copy the code

Used in the project:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Note that the call must come before setContentView
	DensityHelper.setCustomDensity(getApplication(),this);
        setContentView(R.layout.activity_main);
    }
Copy the code

There is insufficient place to point out to everyone, study together.