【Android】通过控制dpi来实现修改分辨率和显示大小app布局错乱问题

时间:2025-01-27 08:36:18

在开发app时,自适应问题是都需要解决的问题,一般大家的实现方式是

1. 给字体不同分辨率设置对应的sp,然后根据不同的分辨率显示对应的大小。

2.设置布局中的单位为dp,这样不同的手机,字体会自动变大变小。

笔者在开发中使用了第二种方法,使用了dp作为单位。这也导致在后续的测试中出现了下面的问题:

1. 当手动修改手机的【设置】中的【显示大小】后,app字体会自动变大导致布局错乱,超出范围

2. 现在很多手机,比如华为,可以在手机的【设置】中手动修改【分辨率】,一旦修改分辨率后,同样也会出现布局字体变大导致布局错乱。

那么怎么能够解决在不进行大的改动的情况下,来解决上面的2个问题,从而使修改【显示大小】后app中的字体保持不变。在修改【分辨率】后,app的字体会自动进行相应的缩放(类似于字体不变,其实是变了),从而保证字体看上去跟原先的没什么两样,保证字体不会因为分辨率修改而变大,导致app布局错乱。

解决方式:

思路:

写一个BaseActivity,所有的Activity都继承它。然后在它的attachBaseContext(Context newBase)声明周期中编写代码,代码通过设置不同的dpi,来保证了上面说的2个问题的解决。

举例说明:

关于问题一:比如,你手机的dpi是440,那么当你把手机的【设置】的【显示大小】的值变大时,手机会自动修改设置dpi为一个值,这也就出现了app字体变大或者变小的现象了。当我们重写attachBaseContext这个生命周期方发后,一旦修改【显示大小】后,便会进出方法,在方法中,我们只需固定dpi为默认的dpi值440就行,这样不管你怎么改,相同分辨率下,dpi不变,你的app中的字体就会不变了。

关于问题二:当手动修改分辨率时,跟修改【显示大小】的机制是不同的,修改分辨率后,因为dpi不会变,这就导致了字体会变大变小。那么怎么实现让字体不变呢(有可能会多少变一点点,因为计算时精确率问题,但总体不影响)。我们通过先获取到手机自带的那个默认分辨率,然后在获取修改后的当前分辨率,算出其缩放率(例如缩放率 = 新的宽度/默认的款的),然后用这个缩放率乘以默认dpi,然后把算出的dpi设置给app,这样就解决问题了。

代码实现:

首先需要一个工具了,这里叫做

package ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;


import ;
import ;


/**
 * Screen Helper Class to get default dpi .
 */
public class ScreenHelper {

    /**
     * Dcm Log Tag .
     */
    private static final String TAG = "ScreenHelper";

    /**
     * The defalut LDPI value .
     */
    private static final int LDPI = DisplayMetrics.DENSITY_DEFAULT;
    /**
     * The defalut HDPI value .
     */
    private static final int HDPI = DisplayMetrics.DENSITY_HIGH;
    /**
     * The defalut XHDPI value .
     */
    private static final int XHDPI = DisplayMetrics.DENSITY_XHIGH;
    /**
     * The defalut XXHDPI value .
     */
    private static final int XXHDPI = DisplayMetrics.DENSITY_XXHIGH;
    /**
     * The 560 dpi value .
     */
    private static final int DPI_560 = DisplayMetrics.DENSITY_560;

    /**
     * The defalut XXXHDPI value .
     */
    private static final int XXXHDPI = DisplayMetrics.DENSITY_XXXHIGH;

    /**
     * The standard 720p device width.
     */
    private static final int DEFALUT_SCREEN_WIDTH = 720;
    /**
     * The standard 1080p device width.
     */
    private static final int MEDIUM_SCREEN_WIDTH = 1080;
    /**
     * The standard 4k device width.
     */
    private static final int HIGH_SCREEN_WIDTH = 1440;
    /**
     * The standard max device width.
     */
    private static final int MAX_SCREEN_WIDTH = 2160;

    /**
     * The Max Screen DP to distinguish phone and tablet.
     */
    private static final int MAX_SCREEN_DP = 600;
    /**
     * Default Mode Name .
     */
    private static final String DEFAULT_MODE = "defaultMode";

    /**
     * getDefaultDpi.<br>
     * Note : get default dpi when Display Size has changed
     *
     * @param context attach activity context
     * @return default dpi
     */
    public int getDefaultDpi(Context context) {
        WindowManager windowManager = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        Display display = ();
        DisplayMetrics metrics = new DisplayMetrics();
        (metrics);

//        boolean changed = isChangedResolution(display, metrics);
        boolean changed = false;
        // if it is tablet,no need to keep current dpi
//        if (isTablet(context)) {
//            return ;
//        } else if (changed) {
//            // get special device dpi
//            return getSpecialDeviceDpi(metrics);
//        } else {
//            return getInitialDisplayDensity(metrics);
//        }
        return getInitialDisplayDensity(metrics);
    }

    private int getInitialDisplayDensity(DisplayMetrics metrics) {
        int physicalDensity = ;
        boolean isError = false;
        try {
            @SuppressLint("PrivateApi") Class<?> clazz = ("");
            try {
                @SuppressLint("DiscouragedPrivateApi") Method method = ("checkService", );
                try {
                    IWindowManager mWindowManager = ((IBinder) (null, Context.WINDOW_SERVICE));
                    try {
                        if (mWindowManager != null) {
                            physicalDensity = (Display.DEFAULT_DISPLAY);
                        }
                    } catch (RemoteException e) {
                        ();
                        isError = true;
                    }
                } catch (IllegalAccessException | InvocationTargetException e) {
                    ();
                    isError = true;
                }
            } catch (NoSuchMethodException e) {
                ();
                isError = true;
            }
        } catch (ClassNotFoundException e) {
            ();
            isError = true;
        }
        if (isError) {
            physicalDensity = getSpecialDeviceDpi(metrics);
        }
        return physicalDensity;
    }

    /**
     * isChangedResolution.<br>
     * Note : check if huawei device
     *
     * @param display view display instance
     * @param displayMetrics view displayMetrics to get some device screen attr
     * @return true is huawei device,false is not
     */
//    private boolean isChangedResolution(Display display, DisplayMetrics displayMetrics) {
//        [] modes = ();
//        if ( > 1) {
//            String displayInfo = ();
//
//            int firstIndex = (DEFAULT_MODE);
//            int length = DEFAULT_MODE.length();
//            String defaultModeInfo = (firstIndex);
//            int firstSymbolIndex = (",");
//            String defaultMode = (length, firstSymbolIndex);
//            int defaultModeIndex = ((" ", ""));
//            for ( mode : modes) {
//                if (() == defaultModeIndex) {
//                    int defaultWidth = ();
//                    return defaultWidth != ;
//                }
//            }
//        }
//
//        // get the init screen Pixels
//        int physicalWidth = ().getPhysicalWidth();
//        int physicalHeight = ().getPhysicalHeight();
//
//        // get current screen Pixels
//        int currentWidth = ;
//        int currentHeight = ;
//
//        //if init size is different with current size, this means resolution
//        //has changed
//        if (physicalWidth != currentWidth && physicalHeight != currentHeight) {
//            return true;
//        }
//
//        return false;
//    }

    /**
     * getSpecialDeviceDpi.<br>
     * Note : when the Resolution could be changed, according to stardand Resolution to
     * get the dpi
     *
     * @param displayMetrics display metrics instances
     * @return standard dpi
     */
    private int getSpecialDeviceDpi(DisplayMetrics displayMetrics) {
        int widthPixels = ;
        int heightPixels = ;

        // get min Pixels.
        int minPixels = (widthPixels, heightPixels);


        // get the apposite dpi
        if (minPixels < DEFALUT_SCREEN_WIDTH) {
            return HDPI;
        } else if (minPixels >= DEFALUT_SCREEN_WIDTH && minPixels < MEDIUM_SCREEN_WIDTH) {
            return XHDPI;
        } else if (minPixels >= MEDIUM_SCREEN_WIDTH && minPixels < HIGH_SCREEN_WIDTH) {
            return XXHDPI;
        } else if (minPixels >= HIGH_SCREEN_WIDTH && minPixels < MAX_SCREEN_WIDTH) {
            return DPI_560;
        } else {
            return XXXHDPI;
        }
    }

    /**
     * isTablet.<br>
     * Note : check if current device is tablet.
     *
     * @param context attach activity context
     * @return true is tablet, false is not
     */
//    private boolean isTablet(Context context) {
//        return ().getBoolean();
//    }


    /**
     * get default resolution beacause HuaWei can setting multi resolution,
     * this method can get the default when you change to another resolution
     * @param context
     * @return
     */
    public int getDefaultResolutionWidth(Context context) {
        WindowManager windowManager = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        Display display = ();

        [] modes = ();

        DisplayMetrics metrics = new DisplayMetrics();
        (metrics);

        if ( > 1) {
            String displayInfo = ();

            int firstIndex = (DEFAULT_MODE);
            int length = DEFAULT_MODE.length();
            String defaultModeInfo = (firstIndex);
            int firstSymbolIndex = (",");
            String defaultMode = (length, firstSymbolIndex);
            int defaultModeIndex = ((" ", ""));
            for ( mode : modes) {
                if (() == defaultModeIndex) {
                    int defaultWidth = ();
                    return defaultWidth;
                }
            }
        }

        // get the init screen Pixels
        int physicalWidth = ().getPhysicalWidth();
        int physicalHeight = ().getPhysicalHeight();



        return physicalWidth;
    }
}

然后在BaseActivity这个基类中重写attachBaseContext方法(其他Activity要继承这个类昂),代码如下:

 @Override
    protected void attachBaseContext(Context newBase) {
        float scale = 0f;
        if (.SDK_INT > 22) {

            Resources res = ();
            Configuration configuration = ();

            ScreenHelper screenHelper = new ScreenHelper();
            int defaultDpi = (newBase);
//             = 0.2f;
//             = defaultDpi;

            int defaultWidth = (newBase);

            DisplayMetrics metrics = ().getDisplayMetrics();
            int width = ;
            if(defaultWidth !=width){
                scale = new BigDecimal((float)width/defaultWidth).setScale(2, BigDecimal.ROUND_HALF_UP).floatValue();
                 = (int)(defaultDpi * scale);
            }else {
                 = defaultDpi;
            }

            Context newContext = (configuration);
            (newContext);
        } else {
            (newBase);
        }

    }

到此就ok了,运行代码,然后修改【分辨率】和【显示大小】看看效果吧,是不是app中的字体都不会在变动了吧。