轻量级的安卓百分比屏幕适配方案SimpleAutoSize

文章介绍了一个简单的适配工具类SimpleAutoSize,它基于设计图的960x540dp基准,通过获取屏幕宽高px动态计算视图的尺寸,以实现不同屏幕尺寸下的适配。这个工具类可以避免代码侵入性,但无法适配xml中的shape资源,需要手动处理。文章提供了初始化和使用方法,并包含处理特殊情况的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AutoSize是基于今日头条的适配方案,但它有一些缺点,比如代码侵入性较强,在使用第三方的View框架时,可能会出现不兼容的情况。

我目前的sdk项目不能使用这样的框架,于是自己做了一个简单的工具类,也能够满足基本需求。

适配方案

以设计图的960 x 540 dp作为基准,在代码中获取屏幕宽高px,遇到需要适配的view时,调用autoSizeLayout()方法自动调整。

  1. autoSizeLayout方法会遍历viewGroup下的所有根节点,获取当前view的内外边距的px

  2. px除以屏幕密度得到当前view在布局文件中设置的dp值

  3. 使用该dp值根据横向或纵向来除以960或540,得出其在设计图中所占比例

  4. 比例乘以当前屏幕的宽高px,即为应该设置的新的px值

优点

  • 代码中只需封装一次,以后布局需要支持的话,都调用autoSizeLayout()方法进行处理即可

  • xml中的dp值可以完全以设计图为准,具体值会在代码中进行计算

  • 集成该框架无需改动现有项目的代码

缺点:

  • xml中的shape资源无法适配,需要手动在代码中处理

public class SimpleAutoSize {

    /**
     * 屏幕宽高px
     */
    private int screenWidth = 0;
    private int screenHeight = 0;

    /**
     * 设计图基准dp
     */
    private int basisWidthDp = 0;
    private int basisHeightDp = 0;

    private float density = 0;

    private onAutoSizeViewListener onAutoSizeViewListener;

    /**
     * 可以直接使用View的宽高px值,乘以该比例,得出相对于960*540屏幕的百分比宽高
     * <p>
     * 计算步骤:
     * 1、布局中的16dp,跑到了1080*1920的屏幕上,成为了24px
     * 2、先24除以屏幕密度1.5,得到布局文件中的dp值16
     * 3、计算16占540的比重
     * 4、结果乘以1080,得到32px,所以16dp在当前屏幕上,按照百分比计算应该是32px
     */
    private float widthRatio = 0;
    private float heightRatio = 0;


    private float screenRatio = 0;

    private static class SingletonHolder {
        private static final SimpleAutoSize instance = new SimpleAutoSize();
    }

    public static SimpleAutoSize getInstance() {
        return SingletonHolder.instance;
    }

    public interface onAutoSizeViewListener {
        public void onAutoSize(View view, float screenRatio);
    }

    public void init(Context context, int basisWidthDp, int basisHeightDp) {
        this.basisWidthDp = basisWidthDp;
        this.basisHeightDp = basisHeightDp;
        calc(context);
    }

    private void calc(Context context){
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        Point screenSize = new Point();
        display.getSize(screenSize);
        screenWidth = screenSize.x;
        screenHeight = screenSize.y;

        this.density = context.getResources().getDisplayMetrics().density;

        widthRatio = (float) screenWidth / basisWidthDp / density; // 再除以屏幕密度,方便后续一步求出结果
        heightRatio = (float) screenHeight / basisHeightDp / density;
        screenRatio = (float) screenWidth / basisWidthDp;
    }

    public SimpleAutoSize.onAutoSizeViewListener getOnAutoSizeViewListener() {
        return onAutoSizeViewListener;
    }

    public void setOnAutoSizeViewListener(SimpleAutoSize.onAutoSizeViewListener onAutoSizeViewListener) {
        this.onAutoSizeViewListener = onAutoSizeViewListener;
    }

    public int getScreenWidth() {
        return screenWidth;
    }

    public int getScreenHeight() {
        return screenHeight;
    }

    public int getBasisWidthDp() {
        return basisWidthDp;
    }

    public int getBasisHeightDp() {
        return basisHeightDp;
    }

    public float getDensity() {
        return density;
    }

    public float getWidthRatio() {
        return widthRatio;
    }

    public float getHeightRatio() {
        return heightRatio;
    }

    public float getScreenRatio() {
        return screenRatio;
    }

    /**
     * 计算自适应高度
     * @param width 真实宽度px
     * @return
     */
    public int calcAutoSizeWidth(int width) {
        return (int) (width * widthRatio);
    }

    /**
     * 计算自适应高度
     * @param height 真实高度px
     * @return
     */
    public int calcAutoSizeHeight(int height) {
        return (int) (height * heightRatio);
    }

    /**
     * 用于判断当前屏幕密度、分辨率是否发生变化
     * @param context
     * @return
     */
    private boolean hasChange(Context context){
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        Point screenSize = new Point();
        display.getSize(screenSize);
        if (screenSize.x != screenWidth || screenSize.y != screenHeight){
            return true;
        }
        if (density != context.getResources().getDisplayMetrics().density){
            return true;
        }
        return false;
    }

    /**
     * 按照比例自动调整布局,使控件在不同密度下显示一致
     *
     * @param view 可以是ViewGroup,也可以是View,如果是ViewGroup,则会递归调用
     */
    public void autoSizeLayout(View view) {
        if (view instanceof ViewGroup) {
            dispatchAutoSizeLayout((ViewGroup) view, null);
        } else {
            loopAutoSizeView(view, null, false);
        }
    }

    /**
     * 按照比例自动调整布局,使控件在不同密度下显示一致
     *
     * @param viewGroup
     * @param onAutoSizeViewListener 监听器,用于针对某个View进行特殊处理,会在调整布局前调用
     */
    public void dispatchAutoSizeLayout(ViewGroup viewGroup, onAutoSizeViewListener onAutoSizeViewListener) {
        // 判断屏幕分辨率,和density是否发生变化,如果发生变化,则重新计算
        if (hasChange(viewGroup.getContext())) {
            calc(viewGroup.getContext());
        }
        loopAutoSizeView(viewGroup, onAutoSizeViewListener, false);
        loopChildAutoSizeLayout(viewGroup, onAutoSizeViewListener);
    }

    private void loopChildAutoSizeLayout(ViewGroup viewGroup, onAutoSizeViewListener onAutoSizeViewListener) {
        int childCount = viewGroup.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childAt = viewGroup.getChildAt(i);
            loopAutoSizeView(childAt, onAutoSizeViewListener, true);
        }
    }

    private void loopAutoSizeView(View childAt, SimpleAutoSize.onAutoSizeViewListener onAutoSizeViewListener, boolean isLoopChild) {
        if (onAutoSizeViewListener != null) {
            onAutoSizeViewListener.onAutoSize(childAt, screenRatio);
        }
        ViewGroup.LayoutParams layoutParams = childAt.getLayoutParams();
        if (layoutParams != null) {
            //            ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) atLayoutParams;
            if (layoutParams.width == layoutParams.height && layoutParams.width > 0) {
                // 如果宽度和高度都是开发者手动在布局文件中设置的具体数值,并且它们相等,说明开发者想要一个正方形,此时我们以较长的一边为基准
                layoutParams.width = (int) (layoutParams.width * widthRatio);
                layoutParams.height = (int) (layoutParams.height * heightRatio);
                if (layoutParams.width > layoutParams.height) {
                    layoutParams.height = layoutParams.width;
                } else {
                    layoutParams.width = layoutParams.height;
                }
            } else {
                if (layoutParams.width > 0) {
                    layoutParams.width = (int) (layoutParams.width * widthRatio);
                }
                if (layoutParams.height > 0) {
                    layoutParams.height = (int) (layoutParams.height * heightRatio);
                }
            }
            if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
                //                Log.i("XmlBaseAdView", "处理前的Margin: " + layoutParams.topMargin + " " + layoutParams.bottomMargin + " " + layoutParams.leftMargin + " " + layoutParams.rightMargin);
                ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams;
                marginLayoutParams.topMargin = (int) (marginLayoutParams.topMargin * heightRatio);
                marginLayoutParams.bottomMargin = (int) (marginLayoutParams.bottomMargin * heightRatio);
                marginLayoutParams.leftMargin = (int) (marginLayoutParams.leftMargin * widthRatio);
                marginLayoutParams.rightMargin = (int) (marginLayoutParams.rightMargin * widthRatio);
//                Log.i("XmlBaseAdView", "处理后的Margin: " + layoutParams.topMargin + " " + layoutParams.bottomMargin + " " + layoutParams.leftMargin + " " + layoutParams.rightMargin);

            }
            childAt.setLayoutParams(layoutParams);
        }
//            Log.i("XmlBaseAdView", "处理前的Padding: " + childAt.getPaddingTop() + " " + childAt.getPaddingBottom() + " " + childAt.getPaddingLeft() + " " + childAt.getPaddingRight());
        float padTop = childAt.getPaddingTop() * heightRatio;
        float padBottom = childAt.getPaddingBottom() * heightRatio;
        float padLeft = childAt.getPaddingLeft() * widthRatio;
        float padRight = childAt.getPaddingRight() * widthRatio;
        childAt.setPadding((int) padLeft, (int) padTop, (int) padRight, (int) padBottom);
//            Log.i("XmlBaseAdView", "处理后的Padding: " + childAt.getPaddingTop() + " " + childAt.getPaddingBottom() + " " + childAt.getPaddingLeft() + " " + childAt.getPaddingRight());

        if (childAt instanceof TextView) {
            // 如果是TextView,还需要处理字体大小
            TextView textView = (TextView) childAt;
            float textSize = textView.getTextSize();
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize /
                    density * screenRatio);
        }
        if (childAt instanceof ViewGroup && isLoopChild) {
            loopChildAutoSizeLayout((ViewGroup) childAt, onAutoSizeViewListener);
        }
    }
}

初始化:

非常简单,只需要一行代码

SimpleAutoSize.getInstance().init(context, 960, 540);

使用:

第一个参数传入要适配的view,一般是根view

        SimpleAutoSize.getInstance().autoSizeLayout(baseAdView, (view, screenRatio) -> {
            Resources resources = context.getResources();
            if (view.getId() == R.id.ll_countdown_tips) {
                view.setBackground(SimpleAutoSize.createRectangleDrawable(resources.getColor(R.color.textBg)
                        , resources.getDimension(R.dimen.dimen_15) /
                                resources.getDisplayMetrics().density * screenRatio));
            }
            if (view.getId() == R.id.tv_show_advert_tag) {
                view.setBackground(SimpleAutoSize.createRectangleDrawable(resources.getColor(R.color.textBg)
                        , resources.getDimension(R.dimen.dimen_3) /
                                resources.getDisplayMetrics().density * screenRatio));
            }
        });

autoSizeLayout第二个参数是onAutoSizeViewListener,用于对某些特殊View做特殊处理,比如我这个框架不支持自动调整Drawable资源的宽高,所以如果需要针对性的调整,可以传入listener自行处理。

我这里是因为shape资源无法适配,所以在代码中进行处理。

大家如需使用的话可以把以下工具方法也添加进去。

    public static GradientDrawable createRectangleDrawable(int color, float radius) {
        return createRectangleDrawable(color, 0, 0, radius);
    }

    /**
     * 创建背景颜色
     *
     * @param color       填充色
     * @param strokeColor 线条颜色
     * @param strokeWidth 线条宽度  单位px
     * @param radius      角度  px
     */
    public static GradientDrawable createRectangleDrawable(int color, int strokeColor, int strokeWidth, float radius) {
        try {
            GradientDrawable radiusBg = new GradientDrawable();
            //设置Shape类型
            radiusBg.setShape(GradientDrawable.RECTANGLE);
            //设置填充颜色
            radiusBg.setColor(color);
            //设置线条粗心和颜色,px
            radiusBg.setStroke(strokeWidth, strokeColor);
            //设置圆角角度,如果每个角度都一样,则使用此方法
            radiusBg.setCornerRadius(radius);
            return radiusBg;
        } catch (Exception e) {
            return new GradientDrawable();
        }
    }


    /**
     * 设置Selector。
     */
    public static StateListDrawable newSelector(int focusColor, int normalColor, float radius) {
        StateListDrawable bg = new StateListDrawable();
        // View.PRESSED_ENABLED_STATE_SET
        bg.addState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}, createRectangleDrawable(focusColor, radius));
        // View.ENABLED_FOCUSED_STATE_SET
        bg.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_focused}, createRectangleDrawable(focusColor, radius));
        // View.ENABLED_STATE_SET
        bg.addState(new int[]{android.R.attr.state_enabled}, createRectangleDrawable(normalColor, radius));
        // View.FOCUSED_STATE_SET
        bg.addState(new int[]{android.R.attr.state_focused}, createRectangleDrawable(focusColor, radius));
        // View.WINDOW_FOCUSED_STATE_SET
        bg.addState(new int[]{android.R.attr.state_window_focused}, createRectangleDrawable(focusColor, radius));
        // View.EMPTY_STATE_SET
        bg.addState(new int[]{}, createRectangleDrawable(normalColor, radius));
        return bg;
    }

    public static GradientDrawable getBgDrawable() {
        return DrawableUtils.createRectangleDrawable(Color.argb(255, 67, 81, 115), 20);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值