移动开发之100%设备屏幕适配方案

移动开发之100%设备屏幕适配方案

前言:本片博客将全面深入分析屏幕适配方案,针对不同项目要求,提供不同的适配框架分析,要求框架能100%适配所有设备。

目录

1.前期技术问题分析

2.适用于中小型项目适配方案

3.适用于大型项目适配方案

一、前期技术问题分析: 

1.如何获取全局Context并解决Application冲突:

前言:从上图Context的类继承关系可以看到,有两个直系子类,分别是包装类和实现类。包装类下可以看到我们熟悉的身影:Service、Application、Activity,所以我们得出结论,Context只在上述环境中存在。因此要想获取全局context,我们常用的方式就是写一个MyApplication继承Application,然后重写onCreate方法提供全局上下文环境,然后

public class MyApplication extends Applocation {

    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
    }

    public static Context getContext() {
        return context;
    }

}

我们要告知系统修改其默认做法,转而启动我们自定义的MyApplication类。具体来说就是在AndroidManifest.xml文件的<application>标签下进行指定。 由于一个工程只能有一个Application,所以当我们使用第三方开源项目如Litepal等对application的配置有需求的资源时,就不能使用上述方法重复配置了,我们只需要在自定义的Application加入支持就可以了。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ......
    <application
        android:name="com.example.test.MyApplication" 
        ......>
    ......
    </application>
</manifest>
public class MyApplication extends Applocation {
    private static Context context;
    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
        LitePalApplication.initialize(context);//加入支持
    }

    public static Context getContext() {
        return context;
    }
}  

2.自定义View相关重点: 

首先上图:

通过上图可以清楚的看出View自定义的过程:

如果是View:首先调用measure方法,接着会调用measure中的onMeasure方法进行测量,测量规则自定义

如果是ViewGroup:首先调用measure方法,接着会调用每个子View的measure方法进行测量,测量规则见下面分析

如何在Activity启动时获取View的宽/高?

1.重写onWindowFocusChanged方法,该方法的含义就是View已经初始化完成了

2.view.post(runnable),该方法的含义就是View已经初始化完成了

3.使用ViewTreeObserver的众多回调可以实现该功能

布局过程的自定义分为三类:

1.重写onMeasure来修改已有View的尺寸

 第一步:调用super.onMeasure方法按原来的计算方法测量一次

第二步:获取到原来的尺寸

第三步:修改成新的尺寸

第四步:保存修改后的尺寸

2.重写onMeasure来全新定义View的尺寸

区别:不需要调用super.onMeasure方法

 第一步:重写onMeasure把自己的尺寸计算出来

第二步:把计算的尺寸通过resolveSize()过滤一遍即可满足父View对子View限制要求的View

3.重写onMeasure和onLayout来全新定义ViewGroup的内部布局

第一步:重写onMeasure来计算内部布局 

  1. 调用每个子View的measure方法,让子View自我测量
  2. 根据子View给出的尺寸,得出子View的位置,并保存他们的位置和尺寸
  3. 根据子View的位置和尺寸计算出自己的尺寸,并用setMeasureDimension保存

 

第二步:重写onLayout来摆放子View 

自定义View须知:

1.让view支持wrap_content

解决方法:只需要对wrap_content模式设置一个默认宽/高即可

2.如果有必要,让view支持padding

解决方法:在onDraw方法中进行运算即可

3.尽量不要在view中使用handler,没必要,因为view内部提供了很多post方法

4.view中如果有线程或者动画,需要及时停止

5.view带有滑动嵌套时,要处理好滑动冲突

二、中小型项目屏幕适配解决方案框架:

框架分析:中小型项目对性能要求相对较低,因此采用修改布局参数的方法进行屏幕适配,该框架存在的性能问题是布局绘制了两次,第一次是setContentView(R.layout.activity_main),第二次是修改布局参数时,因此性能较低。

模块一:实现MyApplication提供全局context,方便使用

模块二:编写UIUtils类计算得到缩放比例

模块三:提供队外调用接口程序,实现框架的调用

模块一:

package com.example.adapter_screenapplication;

import android.app.Application;

/**
 * 为了方便框架内部使用application和context使用
 */
public class JettApplication extends Application {
    private static JettApplication intance;
    public static JettApplication getInstance(){
        return intance;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        intance = this;
    }
}

模块二:

package com.example.adapter_screenapplication;

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import java.lang.reflect.Field;
/**
 * 用来基于美工提供的基准值生成真实设备的宽高值
 */
public class UIUtils {
    //美工提供的标准宽高
    public static final float STANDARD_WIDTH = 1080.0f;
    public static final float STANDARD_HEIGTH = 1872.0f;
    private static final String DIMEN_CLASS = "com.android.internal.R$dimen";
    //实际设备的分辨率是 480    800
    public float displayMetricsWidth;
    public float displayMetricsHeigth;
    //生成单例
    private static UIUtils ourInstance;
    public static UIUtils getInstance(Context context){
        if (ourInstance==null){
            ourInstance = new UIUtils(context);
        }
        return ourInstance;
    }
    private UIUtils(Context context){
        //获取屏幕的真实宽高
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        if (displayMetricsWidth==0.0F||displayMetricsHeigth==0.0F){
            windowManager.getDefaultDisplay().getMetrics(displayMetrics);
            //获取状态栏高度
            int systemHeigth = getSystemHeigth(context);
            //处理真实宽高
            if (displayMetrics.widthPixels>displayMetrics.heightPixels){//横屏
                this.displayMetricsWidth = (float) displayMetrics.heightPixels;
                this.displayMetricsHeigth = (float) displayMetrics.widthPixels-systemHeigth;
            }else {//竖屏
                this.displayMetricsWidth = (float) displayMetrics.widthPixels;
                this.displayMetricsHeigth = (float) displayMetrics.heightPixels-systemHeigth;
            }
        }
    }

    private int getSystemHeigth(Context context) {
       return getValue(context,"com.android.internal.R$dimen","system_bar_heigth",48);
    }

    private int getValue(Context context, String attrGroupClass, String ayyrName, int defValue) {
        try {
            Class c = Class.forName(attrGroupClass);
            Object obj = c.newInstance();
            Field field = c.getField(ayyrName);
            //获取到的是ID
            int x = Integer.parseInt(field.get(obj).toString());
            return context.getResources().getDimensionPixelOffset(x);
        } catch (Exception e) {
            return defValue;
        }
    }
    //获取缩放后的结果
    public float getWidth(float width){
        return width*(this.displayMetricsWidth/STANDARD_WIDTH);
    }
    public float getHeigth(float heigth){
        return heigth*(this.displayMetricsHeigth/STANDARD_HEIGTH);
    }
    public int getWidth(int width){
        return (int) (width*(this.displayMetricsWidth/STANDARD_WIDTH));
    }
    public int getHeigth(int heigth){
        return (int) (heigth*(this.displayMetricsHeigth/STANDARD_HEIGTH));
    }
}

模块三:

package com.example.adapter_screenapplication;

import android.view.View;
import android.widget.LinearLayout;

public class ViewCal {
   //获取调用层的值进行设置
    public static void setViewLinearLayoutParas(View view,int width,int heigth,int topMargin,int bottonMargin,int leftMargin,int rigthMargin){
        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
        if (width!= LinearLayout.LayoutParams.MATCH_PARENT&&width!= LinearLayout.LayoutParams.WRAP_CONTENT){
            layoutParams.width = (int) UIUtils.getInstance(/*提供上下文环境*/JettApplication.getInstance()).getWidth(width);
        }else {
            layoutParams.width = width;
        }
        if (heigth!= LinearLayout.LayoutParams.MATCH_PARENT&&heigth!= LinearLayout.LayoutParams.WRAP_CONTENT){
            layoutParams.height = (int) UIUtils.getInstance(/*提供上下文环境*/JettApplication.getInstance()).getHeigth(heigth);
        }else {
            layoutParams.height = heigth;
        }
        layoutParams.topMargin = (int) UIUtils.getInstance(JettApplication.getInstance()).getHeigth(topMargin);
        layoutParams.bottomMargin = (int) UIUtils.getInstance(JettApplication.getInstance()).getHeigth(bottonMargin);
        layoutParams.leftMargin = (int) UIUtils.getInstance(JettApplication.getInstance()).getWidth(leftMargin);
        layoutParams.rightMargin = (int) UIUtils.getInstance(JettApplication.getInstance()).getWidth(rigthMargin);
        view.setLayoutParams(layoutParams);
    }
}

调用层:

package com.example.adapter_screenapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    TextView textView1;
    TextView textView2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView1 = findViewById(R.id.text1);
        textView2 = findViewById(R.id.text2);
        ViewCal.setViewLinearLayoutParas(textView1,1040,80,10,0,20,10);
        ViewCal.setViewLinearLayoutParas(textView2,400,400,20,0,0,0);
    }
}

执行结果:

 

三、大型项目屏幕适配解决方案框架:

框架分析:该框架从自定义布局入手,在绘制层面进行屏幕适配,因此布局只会绘制一次,提高了应用性能,适用于大型项目屏幕适配解决方案。

模块一:自定义属性(宽/高的百分比)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PercentRelativeLayout" >
        <attr name="layout_widthPercent" format="float"/>
        <attr name="layout_heigthPercent" format="float"/>
    </declare-styleable>
</resources>

模块二:布局中使用自定义属性

<?xml version="1.0" encoding="utf-8"?>
<!--名称空间app-->
<com.example.adapter_screenapplication2.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_widthPercent = "0.5"
        app:layout_heigthPercent = "0.5"
        android:background="@color/colorAccent"
        android:text="Hello World!"/>
</com.example.adapter_screenapplication2.PercentRelativeLayout>

模块三:自定义ViewGroup

package com.example.adapter_screenapplication2;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
//自定义RelativeLayout
public class PercentRelativeLayout extends RelativeLayout {
    //自动生成的构造方法
    public PercentRelativeLayout(Context context) {
        super(context);
    }
    public PercentRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public PercentRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    //重写onMeasure
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = View.MeasureSpec.getSize(widthMeasureSpec);
        int heigth = View.MeasureSpec.getSize(heightMeasureSpec);
        //测量出子控件
        int childCount = this.getChildCount();
        for (int i=0;i<childCount;i++){
            View child = this.getChildAt(i);
            ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
            //把得到的布局参数进行更改
            float widthPercent = 0;
            float heigthPercent = 0;
            if (layoutParams instanceof PercentRelativeLayout.LayoutParams){
                //获取到布局文件上的内容
                widthPercent = ((LayoutParams) layoutParams).getWidthPercent();
                heigthPercent = ((LayoutParams) layoutParams).getHeigthPercent();
            }
            if (widthPercent>0){
                layoutParams.width = (int) (width*widthPercent);
            }
            if (heigthPercent>0){
                layoutParams.height = (int) (heigth*heigthPercent);
            }
        }
        //调用父类的onMeasure方法得到的值就是修改后的值
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    //重写generateLayoutParams:
    @Override
    public RelativeLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
        //这里返回设置好的布局参数
        return new LayoutParams(getContext(),attrs);
    }

    public static class LayoutParams extends RelativeLayout.LayoutParams{

        private float widthPercent;
        private float heigthPercent;
        //set/get方法
        public float getWidthPercent() {
            return widthPercent;
        }
        public void setWidthPercent(float widthPercent) {
            this.widthPercent = widthPercent;
        }
        public float getHeigthPercent() {
            return heigthPercent;
        }
        public void setHeigthPercent(float heigthPercent) {
            this.heigthPercent = heigthPercent;
        }
        //在这里把自定义属性加入
        public LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.PercentRelativeLayout);
            widthPercent = array.getFloat(R.styleable.PercentRelativeLayout_layout_widthPercent,0);
            heigthPercent = array.getFloat(R.styleable.PercentRelativeLayout_layout_heigthPercent,0);
        }
    }
}

运行结果:TextView占屏幕宽高的1/2

总结:

本篇博客主要从适配过程中常见的技术问题或注意事项入手,对两种不同的框架进行对比分析,读者可根据需要选择框架更改代码,实现自己的适配框架。上述框架也是自己借鉴大佬们的框架自己消化理解得到的,如有问题欢迎读者指正。

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值