Android的界面适配,很常见的需求。

各种设备种类和尺寸那么多,基于一种原型设计好的界面,换到另一种设备上去若不适配全乱套了。好在还是有很多方案的,这减少了不少的开发工作量。最流行的就是头条的方案了,使用也超级简单。然而,如果不想引入,还可以简单的一个工具类实现,原理类似于头条的方案。

这里简单介绍下,使用起来也很简单。

原理就是换设备的显示像素密度Density。

头条的UI适配的低成本方案​​AndroidAutoSize也是基于这个原理,只是它封装的更好,稳定性也更好。​

Android智能平板应用,界面适配的另一种轻量级方法_ui

 就是智能平板UI尺寸虽然不一样,但是长宽比例差不多。比如基于1920*1080的界面尺寸设计的应用,现在新的设备屏幕是1366x768。若不加适配肯定显示不全,若能等比例缩小0.71倍就好了,那么方法是有的。就一个类文件Density.java

package com.newcapec.visitorsystem.utils;

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;

public class Density {

/**
* 设置屏幕像素密度
* 思路讲解,计算 B601的老固件像素密度1.5 ,新固件像素密度1 600*32/48 = 400
* 首先我们需要获取当前机型的屏幕密度信息:appDensity,appScaleDensity
*
* 我们的设计尺寸会根据默认机型计算出一个固定的以dp为单位的宽度:WIDTH
*
* 比如:默认机型的宽高为1080*1920,该设备的屏幕密度为3
* 那么WIDTH = 1080/3 = 360dp;因此所有适配机型的宽也就等于360dp。
* 根据 屏幕宽度 / 屏幕密度=WIDTH公式,现在知道屏幕宽度和WIDTH,也就能求出:屏幕密度=屏幕宽度 / WIDHT;
*
* 现在屏幕宽度(dp):targetDensity = displayMetrics.widthPixels / WIDTH 求出。
*
* 接下来,就需要求出适配机型的scaleDensity
*
* appScaleDensity / appDensity = targetScaleDensity / targetDensity ;
*
* targetScaleDensity = targetDensity * (appScaleDensity / appDensity) ;
*
* densityDpi = density * 160 ;
*
* 最后,把获取到的数据,设置到activity的displayMetrics中。
*
* 计算出来后,我们需要在绘制view之前先设置好
*
* 在onCreate中的setContentView之前添加
* ———————————————
*/
private final static float WIDTH = 1920;//适配机型的宽为1920dp,屏幕宽/屏幕密度=1920/1=1920
private static float appDensity;
private static float appScaleDensity;

public static void setDensity(Context appcontx, Context contx) {
//获取当前app的屏幕显示信息
WindowManager wm = (WindowManager)appcontx.getSystemService(Context.WINDOW_SERVICE);
int width = wm.getDefaultDisplay().getWidth();
Log.d("Utils","app width:"+width);
DisplayMetrics displayMetrics = appcontx.getResources().getDisplayMetrics();
appDensity = displayMetrics.density;
appScaleDensity = displayMetrics.scaledDensity;
Log.d("Utils","appDensity:"+appDensity+",appScaleDensity:"+appScaleDensity);
//计算等比缩放后的density和scaleDensity
//WIDTH相对于所有屏幕宽度都是相等的,它是用dp作为单位,所以 屏幕宽度/屏幕密度=WIDTH
//targetDensity = targetWidht/WIDTH
float targetDensity = displayMetrics.widthPixels / WIDTH;
//appScaleDensity/appDensity=targetScaleDensity/targetDensity;
float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
int targetDensityDpi = (int) (targetDensity * 160);

//替换activity的density,appdensity,densityDpi
DisplayMetrics aDisplayMertics = contx.getResources().getDisplayMetrics();
Log.d("Utils","presentDensity:"+aDisplayMertics.density+",presentScaleDensity:"+aDisplayMertics.scaledDensity);
aDisplayMertics.density = targetDensity;
aDisplayMertics.scaledDensity = targetScaleDensity;
aDisplayMertics.densityDpi = targetDensityDpi;

Log.d("Utils","targetDensity:"+targetDensity+",targetScaleDensity:"+targetScaleDensity);
}
}

注意把里面的WIDTH配置为原设计模型的宽度。

原理介绍

首先我们需要获取当前机型的屏幕密度信息:appDensity,appScaleDensity

我们的设计尺寸会根据默认机型计算出一个固定的以dp为单位的宽度:WIDTH

比如:默认机型的宽高为1080*1920,该设备的屏幕密度为3

那么WIDTH = 1080/3 = 360dp;因此所有适配机型的宽也就等于360dp。

根据 屏幕宽度 / 屏幕密度=WIDTH公式,现在知道屏幕宽度和WIDTH,也就能求出:屏幕密度=屏幕宽度 / WIDHT;

现在屏幕宽度(dp):targetDensity = displayMetrics.widthPixels / WIDTH 求出。

接下来,就需要求出适配机型的scaleDensity

appScaleDensity / appDensity = targetScaleDensity / targetDensity ;

targetScaleDensity = targetDensity * (appScaleDensity / appDensity) ;

densityDpi = density * 160 ;

最后,把获取到的数据,设置到activity的displayMetrics中。

计算出来后,我们需要在绘制view之前先设置好。

使用方法

在BaseActivity的onCreate中调用一下就可以了。

Utils.setDensity(App.getContext(),this);

package com.newcapec.visitorsystem.activity.frontscreen;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;

import androidx.annotation.Nullable;

import com.newcapec.visitorsystem.app.App;
import com.newcapec.visitorsystem.threads.ReadCardThread;
import com.newcapec.visitorsystem.utils.Utils;

public class BaseActivity extends Activity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Utils.setDensity(App.getContext(),this);
}
/*
* 隐藏系统键盘
*/
public void hideXtView(){
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
View view = this.getCurrentFocus();
if (view != null) {
InputMethodManager inputMethodManager = (InputMethodManager) this.getSystemService(Activity.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
}
}

以上只是一个简单的方法,可作为一个学习界面适配原理的例子。但是对于自定义view和listview,RecyclerView是失效的。

这里再介绍下基于头条的UI适配的低成本方案​​AndroidAutoSize​​:

​AndroidAutoSize对于自定义view,​​listview,和RecyclerView适配也完全没问题,原理类似。

对于RecyclerView只需在Adapter 的onCreateViewHolder里,调用一下:

AutoSizeCompat.autoConvertDensityOfGlobal(context.getResources());

即可完美适配RecyclerView的UI显示。

​AndroidAutoSize:​

github地址

​https://github.com/JessYanCoding/AndroidAutoSize​

gradle引入

​implementation 'me.jessyan:autosize:1.1.2'​

manifase配置

​如果只使用副单位 (pt、in、mm) 就可以直接以像素作为单位填写设计图的尺寸, 不需再把像素化为 dp​

<!-- 如果只使用副单位 (pt、in、mm) 就可以直接以像素作为单位填写设计图的尺寸, 不需再把像素转化为 dp-->
<!-- 用mm副单位开发,这里配置和设计稿一样的尺寸px , 1920 x 1080px -->
<manifest>
<application>
<meta-data
android:name="design_width_in_dp"
android:value="1080"/>
<meta-data
android:name="design_height_in_dp"
android:value="1920"/>
</application>
</manifest>

注意上面的meta-data是原始设计尺寸的宽,高。可不用设置成新设备的屏幕尺寸了。

以上基本满足你的基本需求了,若还有问题,可以参见这个贴,汇总了常见问题的解决办法:

​https://github.com/JessYanCoding/AndroidAutoSize/issues/13​

还有一种屏幕适配方案,原理跟上述的差不多,是AndroidScreenAdaptation。

AndroidScreenAdaptation:

地址:https://github.com/yatoooon/AndroidScreenAdaptation

AndroidScreenAdaptation库特点:

完全不用改变自己的布局编写习惯,你原先是怎么写布局,就怎么写布局.不用去继承适配类,不用在最外层包裹适配布局,不用新建茫茫多的分辨率适配文件夹,不要求强制使用px为单位,可以实时预览布局,全面屏或带虚拟按键手机适配也没问题。

使用:

1.添加依赖

implementation 'me.yatoooon:screenadaptation:1.1.1'

2.初始化工具类

(1)创建自己的application继承Application

public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
ScreenAdapterTools.init(this);
}

//如果应用屏幕固定了某个方向不旋转的话(比如qq和微信),下面可不写.
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ScreenAdapterTools.getInstance().reset(this);
}
}

(2)在AndroidManifest.xml文件中声明使用你自己创建的application并且添加meta-data数据,例子上标明了这些数据的代表的意义

<application
android:name=".App"
.....
<meta-data
android:name="designwidth"
android:value="1080" /> //设计图的宽,单位是像素,推荐用markman测量,量出来如果是750px那么请尽量去找ui设计师要一份android的设计图.
<meta-data
android:name="designdpi"
android:value="480" /> //设计图对应的标准dpi,根据下面的那张图找到对应的dpi,比如1080就对应480dpi,如果拿到的是其他宽度的设计图,那么选择一个相近的dpi就好了
<meta-data
android:name="fontsize"
android:value="1.0" /> //全局字体的大小倍数,有时候老板会觉得你的所有的字小了或者大了,你总不能一个一个去改吧
<meta-data
android:name="unit"
android:value="px" /> //你的布局里面用的是px这就写px,你的布局里面用的是dp这就写dp,要统一,不要一会儿px一会儿dp,字体也用px或者dp,不要用sp,微信qq用的肯定不是sp.
</application>

Android智能平板应用,界面适配的另一种轻量级方法_ui_02

3. 开始使用

(1)在Activity中,找到setcontentview(R.layout.xxxxxx)

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_dp);
//在setContentView();后面加上下面这句话
ScreenAdapterTools.getInstance().loadView((ViewGroup) getWindow().getDecorView());

}
}

注: 自定义view的话,在 ScreenAdapterTools.getInstance().loadView((ViewGroup) view); 外面包裹一层判断如下,不然在使用自定义view编写布局文件时预览xml会有问题!但不影响真机运行效果.

if (!isInEditMode()) {
ScreenAdapterTools.getInstance().loadView((ViewGroup) view);
}

原理

1. px是分辨率的单位 比如现在主流手机分辨率1080*1920.

2. dp是安卓开发专有的单位 在 不同的手机下 1dp = 不同的 px.

3. sp是字体大小(前面清单文件中要求字体也用dp或者px),sp随系统字体大小变化而变化,但据我观察,像微信qq这些app的字体是不随系统显示字体大小变化的.

### 本库是按照设计图的宽度和对应标准dpi来适配的(宽度增加或减少,高度同比例增加或减少),在不同的分辨率,不同ppi(手机屏幕密度,又称为dpi),不同最小宽度(有的人喜欢去调开发者选项下面的最小宽度,主流手机默认为360dp)的手机下都做到了适配。

引用:

​今日头条屏幕适配方案终极版 AndroidAutoSize-玩Android - wanandroid.com​​​​安卓适配AutoSize详解_xxdw1992的博客_me.jessyan:autosize​​​​Android安卓中最棒的屏幕适配AndroidScreenAdaptation_快乐李同学的博客-CSDN博客_android screen​

​屏幕适配:修改屏幕像素密度,随便设dp_Android架构师丨小熊的博客​

​Andoid屏幕适配终极手段(小编用过最得劲的dp适配)_奋斗的IT青年_最小宽度多少dp让手机流畅​

​Android AutoLayout全新的适配方式 堪称适配终结者_Hongyang_autolayout​