屏幕适配
背景:设计师只给出一个针对720 * 1080的屏幕的视图;
问题:如何解决在不同分辨率手机的显示问题;
方案一:由于View的宽高 与 摆放位置,都是由ViewGroup 控制的,我们是不是能在测量阶段就对View 的宽高尺寸进行适配,答案是可以的。接下来,让我们来实现可以自动适配屏幕的根布局:
为了计算缩放比,我们需要获取屏幕的宽高,我们定义一个工具类,来实现屏幕测量的相关功能:
public class ScreenUtils {
private final static float WIDTH =720; // 给定设计图对应的屏幕宽高
private final static float HEIGHT =1080;
private static ScreenUtils screenUtils;
private float heightScale; //高 缩放比例
private float widthScale; // 宽 缩放比例
private Context context;
private ScreenUtils(Context context) {
if (context instanceof Application) {
this.context =context;
}else {
this.context =context.getApplicationContext();
}
initScreentAboutParms();
}
public static ScreenUtils getInstance(Context context) {
if (screenUtils ==null) {
synchronized (ScreenUtils.class) {
screenUtils =new ScreenUtils(context);
}
}
return screenUtils;
}
/**
* 处理 宽、高 的缩放比
*/
private void initScreentAboutParms() {
DisplayMetrics displayMetrics =new DisplayMetrics();
WindowManager windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
float screenWidthPixels =displayMetrics.widthPixels;
float screenHeightPixels =displayMetrics.heightPixels;
// 考虑到横竖屏的问题,所以我们要考虑两种情况
if (screenWidthPixels >screenHeightPixels) {// 横屏
heightScale =screenWidthPixels/HEIGHT;
widthScale =screenHeightPixels/WIDTH;
}else {
heightScale =screenHeightPixels/HEIGHT;
widthScale =screenWidthPixels/WIDTH;
}
}
public float getWidthScale() {
return widthScale;
}
public float getHeightScale() {
return heightScale;
}
}
接下来我们定义一个自定义ViewGroup来使用上述的方法,覆写onMeasure方法,实现屏幕适配,具体的实现由于难度不大,我就直接贴代码了:
/**
* 创建者:qinyafei
* 创建时间: 2019/12/1
* 描述: 自定义根布局,实现屏幕适配
* 版本信息:1.0.0
**/
public class MyScaleRelativeLayout extends RelativeLayout {
static final String TAG ="QYF_MyScaleRelativeLayout";
private boolean flag;
public MyScaleRelativeLayout(Context context) {
super(context);
Log.i(TAG, "MyScaleRelativeLayout: ");
}
public MyScaleRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i(TAG, "MyScaleRelativeLayout: 1111111111111");
}
/**
* 此方法是做 子View 的测量,我们需要再次做适配
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(!flag){// 此标致,是为了保证 ,View 只被测量一次,因为 onMeasure 在实际的运行过程中,会运行 两次。具体原因 大家可以 去看下View的绘制流程;
float scaleHeight =ScreenUtils.getInstance(getContext()).getHeightScale();
float scaleWidth =ScreenUtils.getInstance(getContext()).getWidthScale();
Log.i(TAG, "onMeasure: " +scaleHeight +" " +scaleWidth);
int childCount = getChildCount();
for(int i =0; i < childCount ; i++){
View childView = getChildAt(i);
LayoutParams layoutParams = (RelativeLayout.LayoutParams)childView.getLayoutParams();
layoutParams.width = (int) (layoutParams.width *scaleWidth);
layoutParams.height = (int) (layoutParams.height *scaleHeight);
layoutParams.bottomMargin = (int) (layoutParams.bottomMargin *scaleHeight);
layoutParams.topMargin = (int) (layoutParams.bottomMargin *scaleHeight);
layoutParams.leftMargin = (int) (layoutParams.leftMargin *scaleWidth);
layoutParams.rightMargin = (int) (layoutParams.rightMargin *scaleWidth);
}
flag =true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
具体运行效果如下:
布局代码,为了看起来更直观,我们的宽高使用px,为了对应工具类中的设计稿宽高。
<?xml version="1.0" encoding="utf-8"?>
<com.example.myscreen.widgets.MyScaleRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:text="111111111111111"
android:background="@color/colorAccent"
android:layout_width="360px"
android:layout_height="360px"/>
</com.example.myscreen.widgets.MyScaleRelativeLayout>
页面显示如下:
在开始方案二之前,我们了解下DisplayMetrics的相关属性。我们先了解下这个类的意义 这个类在注释中的描述是A structure describing general information about a display, such as its* size, density, and font scaling大概意思是一个描述屏幕显示信息的类,比如尺寸、密度、字体缩放。在了解了类的用处之后,我们是不是可以通过修改属性中的屏幕密度来实现屏幕是呢(有关屏幕密度与显示效果的关系,大家可以去查阅相关资料)? 答案是可以的。那我们就可以使用第二种方案来实现屏幕适配。
方案二通过修改屏幕密度来显示屏幕适配
具体实现,我就不一一介绍了,直接上代码
/**
* 创建者:qinyafei
* 创建时间: 2019/12/1
* 描述: 修改屏幕密度,实现屏幕适配
* 版本信息:1.0.0
**/
public class MyDensity {
private static final int WIDTH =720; // 参考设备宽度
private static final int HEIGHT =1080; // 参考设备的高度
private static float appDesity;// View 显示密度
private static float appScaleDensity; // 字体密度,默认是与 View 显示相同
public static void setMyDensity(final Application application, Activity activity){
DisplayMetrics displayMetrics =application.getResources().getDisplayMetrics();
if(appDesity ==0){// 还没进行处理
appDesity =displayMetrics.density;
appScaleDensity =displayMetrics.scaledDensity;
// 添加字体变化监听回调,解决 字体 不能进行适配缩放的问题
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if(newConfig !=null &&newConfig.fontScale >0){
appScaleDensity =application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
float targetDensity =displayMetrics.widthPixels /WIDTH;// View 对应的缩放比例
float tartgetScaleDensity =targetDensity * (appScaleDensity /appDesity);
int targetDensityDpi = (int) (targetDensity *160);
// 替换 Activity的density ,scalDensity, densityDpi
DisplayMetrics dm =activity.getResources().getDisplayMetrics();
dm.density =targetDensity;
dm.scaledDensity =tartgetScaleDensity;
dm.densityDpi =targetDensityDpi;
}
}
具体用法是在每次setContentView之前调用,具体效果就不做展示了,感兴趣的同学自己手敲代码实现,看下效果。
除去以上两种方案,我知道的还有谷歌的百分比方案。感兴趣的同学可以参考着谷歌的,自己尝试写一个,我就不在介绍了。