今日头条适配方案
一、屏幕适配原理
1、Android中的dp、px、dpi、desity关系
px = density * dp;
density = dpi / 160;
px = dp * (dpi / 160);
其中dpi是根据屏幕的真实分辨率和尺寸计算的,每个设备可能都不一样
2、为什么要算出 density,这和屏幕适配有什么关系呢?
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
不管你在布局文件中填写的是什么单位,最后都会被转化为px,系统就是通过上面的方法,将你在项目中任何地方填写的单位都转换为px 的,所以我们常用的px转dp的公式dp = px / density,就是根据上面的方法得来的
3、修改density的大小,保证在所有的设备上计算出来的px 的值正好是屏幕宽度(解决方案)
(1)、Denstiy是DisplayMetrics 中的成员变量,DisplayMetrics 实例通过 Resources#getDisplayMetrics() 可以获得,而Resouces通过Activity或者Application的Context获得
DisplayMetrics#density 就是上述的density
DisplayMetrics#densityDpi 就是上述的dpi
DisplayMetrics#scaledDensity 字体的缩放因子
正常情况下和density相等,但是调节系统字体大小后会改变这个值
(2)假设按照设计图是320dp,依据宽度来适配
注:今日头条的适配方式,今日头条适配方案默认项目中只能以高或宽中的一个作为基准,进行适配
那么适配后的 density = 设备真实宽(单位px) / 320,接下来只需要把我们计算好的 density 在系统中修改下即可,代码实现如下
// 获取原始密度大小
private static float sRoncompatDennsity;
// 缩放比例因子
private static float sRoncompatScaledDensity;
private void setCustomDensity(@NonNull Activity activity,final @NonNull Application application) {
//application
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if (sRoncompatDennsity == 0) {
sRoncompatDennsity = appDisplayMetrics.density;
sRoncompatScaledDensity = appDisplayMetrics.scaledDensity;
//
当应用程序运行中,系统配置发生改变时,系统回调用此方法
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig !=null && newConfig.fontScale > 0) {
// 当系统配置发生改变,缩放因子也随之改变
sRoncompatScaledDensity = application.getResources()
.getDisplayMetrics().scaledDensity;
}
}
});
}
//
计算宽为320dp
final float targetDensity = appDisplayMetrics.widthPixels / 320;
final float targetScaledDensity = targetDensity * (sRoncompatScaledDensity /sRoncompatDennsity);
final int targetDensityDpi = (int) (targetDensity * 160);
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
appDisplayMetrics.scaledDensity = targetScaledDensity;
//activity
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
}
AndroidAutoSize 使用注意事项和原理
项目地址:https://github.com/JessYanCoding/AndroidAutoSize
1、AndroidAutosize使用注意事项
我们还是依照宽度为320dp为基准说明
[if !supportLists]a、 [endif]当我们的设计图宽度为填充整个屏幕的宽度时,我们的宽度写成layout_width="320dp"和layout_width="match_parent"都可以
[if !supportLists]b、 [endif]对于固定尺寸的图片或者其他控件,如果mark图上面明确标注了宽、高,我就按照设计图宽和高的1\2设置对应的宽、高(单位为:dp)
2、AndroidAutosize原理
AndroidAntuosize 第三方库的实现基本原理和今日头条适配原理一样,其实就是对今日头条适配方案的封装
1、通过声明{@link
InitProvider} 自动启动初始化
Provider是由ActivityThread负责启动的,ActivityThread对应应用进程的主线程,即在应用进程启动时,会将ContentProvider启动起来。
@Override
public boolean onCreate() {
AutoSizeConfig.getInstance()
.setLog(true)
.init((Application) getContext().getApplicationContext())
.setUseDeviceSize(false);
return true;
}
2、配置AutosizeConfig中完成具体的初始化工作
注意:初始化方法只能调用一次, 否则报错,对activity和fragment的生命周期注册监听,初始化默认的设配策略(在manifest 中配置)如果对某个activiyh 进行了自定义策略,则使用自定义策略
/**
*框架会在APP 启动时自动调用此方法进行初始化, 使用者无需手动初始化, 初始化方法只能调用一次, 否则报错
*
* @param application {@link Application}
* @param isBaseOnWidth详情请查看{@link #isBaseOnWidth} 的注释
* @param strategy {@link AutoAdaptStrategy},传{@code null} 则使用{@link DefaultAutoAdaptStrategy}
*/
AutoSizeConfig init(final Application application, boolean isBaseOnWidth, AutoAdaptStrategy strategy) {
Preconditions.checkArgument(mInitDensity == -1, "AutoSizeConfig#init() can only be called once");
Preconditions.checkNotNull(application, "application == null");
this.mApplication = application;
this.isBaseOnWidth = isBaseOnWidth;
final DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
//从manifest中获取配置的宽或者高
getMetaData(application);
//获取屏幕的真实宽度和高度
int[] screenSize = ScreenUtils.getScreenSize(application);
mScreenWidth = screenSize[0];
mScreenHeight = screenSize[1];
mInitDensity = displayMetrics.density;
mInitDensityDpi = displayMetrics.densityDpi;
mInitScaledDensity = displayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null) {
if (newConfig.fontScale > 0) {
mInitScaledDensity =
Resources.getSystem().getDisplayMetrics().scaledDensity;
int[] screenSize = ScreenUtils.getScreenSize(application);
mScreenWidth = screenSize[0];
mScreenHeight = screenSize[1];
}
}
@Override
public void onLowMemory() {
}
});
mActivityLifecycleCallbacks = new ActivityLifecycleCallbacksImpl(strategy == null ? new DefaultAutoAdaptStrategy() : strategy);
//对Activity的生命周期事件进行监听
application.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
return this;
}
3、获取使用者在Androidmanifest中填写的meta信息
android:value="320"/>
android:value="568"/>
private void getMetaData(final Context context) {
new Thread(new Runnable() {
@Override
public void run() {
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo;
try {
applicationInfo = packageManager.getApplicationInfo(context
.getPackageName(), PackageManager.GET_META_DATA);
if (applicationInfo != null && applicationInfo.metaData != null) {
if (applicationInfo.metaData.containsKey(KEY_DESIGN_WIDTH_IN_DP)) {
mDesignWidthInDp = (int) applicationInfo.metaData.get(KEY_DESIGN_WIDTH_IN_DP);
}
if (applicationInfo.metaData.containsKey(KEY_DESIGN_HEIGHT_IN_DP)) {
mDesignHeightInDp = (int) applicationInfo.metaData.get(KEY_DESIGN_HEIGHT_IN_DP);
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}).start();
}
4、默认配置策略
这里是今日头条适配方案的核心代码, 核心在于根据当前设备的实际情况做自动计算并转换DisplayMetrics#density、DisplayMetrics#scaledDensity、DisplayMetrics#densityDpi这三个值, 有兴趣请看下面的链接设计图上的设计尺寸, sizeInDp设计图上的设计尺寸,单位dp, 如果isBaseOnWidth设置为true,则应该填写设计图的总宽度, 如果isBaseOnWidth 设置为false, sizeInDp 则应该填写设计图的总高度,isBaseOnWidth 是否按照宽度进行等比例适配, true为以宽度进行等比例适配, false 为以高度进行等比例适配
public static void autoConvertDensity(Activity activity, float sizeInDp, boolean isBaseOnWidth) {
Preconditions.checkNotNull(activity, "activity == null");
int screenSize = isBaseOnWidth ? AutoSizeConfig.getInstance().getScreenWidth()
: AutoSizeConfig.getInstance().getScreenHeight();
String key = sizeInDp + "|" + isBaseOnWidth + "|"
+ AutoSizeConfig.getInstance().isUseDeviceSize() + "|"
+ AutoSizeConfig.getInstance().getInitScaledDensity() + "|"
+ screenSize;
DisplayMetricsInfo displayMetricsInfo = mCache.get(key);
float targetDensity = 0;
int targetDensityDpi = 0;
float targetScaledDensity = 0;
if (displayMetricsInfo == null) {
if (isBaseOnWidth) {
targetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp;
} else {
targetDensity = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / sizeInDp;
}
targetScaledDensity = targetDensity * (AutoSizeConfig.getInstance().
getInitScaledDensity() * 1.0f / AutoSizeConfig.getInstance().getInitDensity());
targetDensityDpi = (int) (targetDensity * 160);
mCache.put(key, new DisplayMetricsInfo(targetDensity, targetDensityDpi, targetScaledDensity));
} else {
targetDensity = displayMetricsInfo.density;
targetDensityDpi = displayMetricsInfo.densityDpi;
targetScaledDensity = displayMetricsInfo.scaledDensity;
}
//对activity和 application的DisplayMetrics设置参数
setDensity(activity, targetDensity, targetDensityDpi, targetScaledDensity);
}
网友评论