2.1.1_Android屏幕适配大全

布局适配

  • 避免写死空间尺寸,使用wrap_content,match_parent
  • LinearLayout xxx:layout_weight=“0.5”
  • RelativeLayout xxx:layout_centerInParent=“true”
  • ContraintLayout xxx:layout_constraintLeft_toLeftOf=“parent”
  • Percent-support-lib xxx:layout_widthPercent=“30%”

图片资源适配

  • .9图或者SVG图实现缩放
  • 备用位图匹配不同分辨率

用户流程适配

  • 根据业务逻辑执行不同的跳转逻辑
  • 根据别名展示不同的界面

限定符的适配

  • 分辨率适配 drawable-hdpi,drawable-xdpi …
  • 尺寸限定符 layout-small,layout-large …
  • 最小宽度限定符 values-sw360dp,values-sw384dp …
  • 屏幕方向限定符 layout-land,layout-port
    缺点:
  • 增加APK大小,适配机型越多的话,需要的XML也就越多
  • 适配所有机型的分辨率,xml文件加起来有近3M
  • 不能适配奇葩机型,如手表

刘海屏适配

  • Android 9.0 官方适配
  • 9.0以前华为、Oppo、Vivo、小米等各家自己的刘海屏方案

屏幕适配-自定义View

以一个特定宽度尺寸的设备为参考,在View的加载过程,根据当前设备的实际像素换算出目标像素,再作用在控件上。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    float scaleX = 100;//获取横向缩放比
    float scaleY = 100;//获得竖向缩放比
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i); //重新设置子View的布局属性,再进行View的测量
        LayoutParams lp = (LayoutParams) child.getLayoutParams();
        lp.width = (int)(lp.width * scaleX); //换算宽度目标值
        lp.height = (int) (lp.height * scaleY); //换算高度目标值
        lp.topMargin = (int) (lp.height * scaleY); //换算四周间距的目标值
        //....
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

屏幕适配-百分比布局

测量

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //获取父容器的尺寸
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int count = getChildCount();
    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        ViewGroup.LayoutParams params = child.getLayoutParams();
        //如果说是百分比布局属性
        if (checkLayoutParams(params)){
            LayoutParams lp = (LayoutParams)params;
             float widthPercent = lp.widthPercent;
             float heightPercent = lp.heightPercent;
             float marginLeftPercent = lp.marginLeftPercent;
             float marginRightPercent= lp.marginRightPercent;
             float marginTopPercent= lp.marginTopPercent;
             float marginBottomPercent = lp.marginBottomPercent;

             if (widthPercent > 0){
                 params.width = (int) (widthSize * widthPercent);
             }
			 //...	
        }
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

解析LayoutParams

@Override
   protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
       return p instanceof LayoutParams;
   }

   public LayoutParams generateLayoutParams(AttributeSet attrs){
       return new LayoutParams(getContext(), attrs);
   }

   public static class LayoutParams extends RelativeLayout.LayoutParams{

       private float widthPercent;
       private float heightPercent;
       private float marginLeftPercent;
       private float marginRightPercent;
       private float marginTopPercent;
       private float marginBottomPercent;

       public LayoutParams(Context c, AttributeSet attrs) {
           super(c, attrs);
           //解析自定义属性
           TypedArray a = c.obtainStyledAttributes(attrs,R.styleable.PercentLayout);
           widthPercent = a.getFloat(R.styleable.PercentLayout_widthPercent, 0);
           heightPercent = a.getFloat(R.styleable.PercentLayout_heightPercent, 0);
           marginLeftPercent = a.getFloat(R.styleable.PercentLayout_marginLeftPercent, 0);
           marginRightPercent = a.getFloat(R.styleable.PercentLayout_marginRightPercent, 0);
           marginTopPercent = a.getFloat(R.styleable.PercentLayout_marginTopPercent, 0);
           marginBottomPercent = a.getFloat(R.styleable.PercentLayout_marginBottomPercent, 0);
           a.recycle();
       }
   }
源码分析
-> setContentView
    -> PhoneWindow.setContentView
        -> mLayoutInflater.inflate
            -> ViewGroup.generateLayoutParams  

public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new LayoutParams(getContext(), attrs);
}  

可以看到,LayoutParams是由View的父容器来生成的,通过new LayoutParams的方式。

屏幕适配-像素密度

修改density、scaleDensity、densityDpi,直接更改系统内部对于目标尺寸而言的像素密度。

 public class Density {
    private static final float  WIDTH = 320;//参考设备的宽,单位是dp 320 / 2 = 160

    private static float appDensity;//表示屏幕密度
    private static float appScaleDensity; //字体缩放比例,默认appDensity

    public static void setDensity(final Application application, Activity activity){
        //获取当前app的屏幕显示信息
        DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
        if (appDensity == 0){
            //初始化赋值操作
            appDensity = displayMetrics.density;
            appScaleDensity = displayMetrics.scaledDensity;

            //添加字体变化监听回调
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(Configuration newConfig) {
                    //字体发生更改,重新对scaleDensity进行赋值
                    if (newConfig != null && newConfig.fontScale > 0){
                        appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                @Override
                public void onLowMemory() {

                }
            });
        }

        //计算目标值density, scaleDensity, densityDpi
        float targetDensity = displayMetrics.widthPixels / WIDTH; // 1080 / 360 = 3.0
        float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
        int targetDensityDpi = (int) (targetDensity * 160);

        //替换Activity的density, scaleDensity, densityDpi
        DisplayMetrics dm = activity.getResources().getDisplayMetrics();
        dm.density = targetDensity;
        dm.scaledDensity = targetScaleDensity;
        dm.densityDpi = targetDensityDpi;
    }
}  

然后在Application中设置

registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
	@Override
	public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
	   Density.setDensity(App.this, activity);
	}
	//...
});

或者在BaseActivity中设置

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Density.setDensity(getApplication(),this);
}  

这样,就可以在XML中,根据设计稿中的WIDTH,直接按照设计稿的尺寸来编写布局了,并能很好地进行屏幕适配。

   <TextView
        android:id="@+id/text"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:text="Hello World!"
        android:background="@color/colorAccent"/>  

屏幕适配-动态代码适配

直接在java代码中进行适配

public class UIUtils {
    //工具类
    private static UIUtils instance ;
    //ios  标准
    public static final float STANDARD_WIDTH=1080f;
    public static final float STANDARD_HEIGHT=1920f;

    //实际设备信息  赋值    他是不知道横竖  1080  1920
    //width  1920   高度 1080
    public static float displayMetricsWidth;
    public static float displayMetricsHeight;
    public static float systemBarHeight;
    //applicaiton
    public static UIUtils getInstance(Context context){
        if(instance==null){
            instance=new UIUtils(context);
        }
        return instance;
    }
    public static UIUtils notityInstance(Context context){
        instance=new UIUtils(context);
        return instance;
    }
    //activity
    public static UIUtils getInstance() {
        if (instance == null) {
            throw new RuntimeException("UiUtil应该先调用含有构造方法进行初始化");
        }
        return instance;
    }
    private UIUtils(Context context) {
        //计算缩放系数
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics=new DisplayMetrics();
        if(displayMetricsWidth==0.0f || displayMetricsHeight==0.0f){
            //在这里得到设备的真实值
            windowManager.getDefaultDisplay().getMetrics(displayMetrics);
            //状态栏高度
            systemBarHeight=getSystemBarHeight(context);
            //横屏
            if(displayMetrics.widthPixels>displayMetrics.heightPixels){
                this.displayMetricsWidth=(float)(displayMetrics.heightPixels);
                this.displayMetricsHeight=(float)(displayMetrics.widthPixels-systemBarHeight);
            }else {
				//竖屏
                this.displayMetricsWidth=(float)(displayMetrics.widthPixels);
                this.displayMetricsHeight=(float)(displayMetrics.heightPixels-systemBarHeight);
            }
        }
    }

    public float getHorizontalScaleValue(){
        return  ((float)(displayMetricsWidth)) / STANDARD_WIDTH;
    }
    public float getVerticalScaleValue(){
        return ((float)(displayMetricsHeight))/(STANDARD_HEIGHT-systemBarHeight);
    }
    private int getSystemBarHeight(Context context){
        return getValue(context,"com.android.internal.R$dimen","system_bar_height",48);
    }

    public int getWidth(int width) {
        return Math.round((float)width * this.displayMetricsWidth / STANDARD_WIDTH);
    }
    public int getHeight(int height) {
        return Math.round((float)height * this.displayMetricsHeight / (STANDARD_HEIGHT-systemBarHeight));
    }


    private int getValue(Context context, String dimeClass, String system_bar_height, int defaultValue) {
        //com.android.internal.R$dimen    system_bar_height   状态栏的高度
        try {
            Class<?> clz=Class.forName(dimeClass);
            Object object = clz.newInstance();
            Field field=clz.getField(system_bar_height);
            int id=Integer.parseInt(field.get(object).toString());
            return context.getResources().getDimensionPixelSize(id);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return defaultValue;
    }
}

public class ViewCalculateUtil {
     /**
     * 设置LinearLayout中 view的高度宽度
     *
     * @param view
     * @param width
     * @param height
     */
    public static void setViewLinearLayoutParam(View view, int width, int height, int topMargin, int bottomMargin, int lefMargin,
                                                int rightMargin) {

        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
        if (width != RelativeLayout.LayoutParams.MATCH_PARENT && width != RelativeLayout.LayoutParams.WRAP_CONTENT && width != RelativeLayout.LayoutParams.FILL_PARENT) {
            layoutParams.width = UIUtils.getInstance().getWidth(width);
        } else {
            layoutParams.width = width;
        }
        if (height != RelativeLayout.LayoutParams.MATCH_PARENT && height != RelativeLayout.LayoutParams.WRAP_CONTENT && height != RelativeLayout.LayoutParams.FILL_PARENT) {
            layoutParams.height = UIUtils.getInstance().getHeight(height);
        } else {
            layoutParams.height = height;
        }

        layoutParams.topMargin = UIUtils.getInstance().getHeight(topMargin);
        layoutParams.bottomMargin = UIUtils.getInstance().getHeight(bottomMargin);
        layoutParams.leftMargin = UIUtils.getInstance().getWidth(lefMargin);
        layoutParams.rightMargin = UIUtils.getInstance().getWidth(rightMargin);
        view.setLayoutParams(layoutParams);
    }
}

然后进行使用即可

public class MainActivity extends AppCompatActivity {
    private TextView tvText3;
    private TextView tvText4;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        UIUtils.getInstance(this.getApplicationContext());
        setContentView(R.layout.activity_main);
        tvText3 = findViewById(R.id.tvText3);
        tvText4 = findViewById(R.id.tvText4);
        ViewCalculateUtil.setViewLinearLayoutParam(tvText3, 540, 100, 0, 0, 0, 0);
        ViewCalculateUtil.setViewLinearLayoutParam(tvText4, 1080, 100, 0, 0, 0, 0);
        ViewCalculateUtil.setTextSize(tvText3,30);
    }
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        UIUtils.notityInstance(this);
    }
}

刘海屏适配

详见 2.1.2 Android刘海屏适配

字体适配

详见 Android 中的字体大小适配

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

氦客

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值