前言
在android开发的过程中,我们在做不同屏幕适配的时候,一般都会按照android的标准,去设置不同的layout或者在不同的values-文件夹下设置不同的dimens来满足需求,但是这里有一个问题就是重复的劳动,效率不是特别高,所以开始的时候有一个思路就是按照UI设计的一套标准去设置一套某个屏幕下的标准的dimens,然后再写一个自动化的脚本,根据不同屏幕的参数去自动化生产不同的dimens,但是也比较麻烦。所以想着从源码的角度看下,有什么可行的思路。
源码分析
我们在编写layout时会在xml中设置一些属性,比如某个控件的宽度,高度等,一般以dip作为单位,以View为例子,我们来追下源码:在View的构造函数中,有这段代码:
public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
//无辜的省略号......
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_padding:
padding = a.getDimensionPixelSize(attr, -1);
mUserPaddingLeftInitial = padding;
mUserPaddingRightInitial = padding;
leftPaddingDefined = true;
rightPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingLeft:
leftPadding = a.getDimensionPixelSize(attr, -1);
mUserPaddingLeftInitial = leftPadding;
leftPaddingDefined = true;
break;
//无辜的省略号......
}
}
}
ok,有一个获取padding 的东东,下面我们来看下 TypedArray 的getDimensionPixelSize()方法:
public int getDimensionPixelSize(int index, int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
data[index+AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
throw new RuntimeException("Failed to resolve attribute at index " + index);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
+ Integer.toHexString(type));
}
ok,我们知道哈,dip是像素无关的,是一个比值嘛,那肯定就和屏幕的density有关啦,刚好有一个 mMetrics ,看下里面如何实现:
public static int complexToDimensionPixelSize(int data,
DisplayMetrics metrics) {
final float value = complexToFloat(data);
final float f = applyDimension(
(data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
value,
metrics);
final int res = (int)(f+0.5f);
if (res != 0) return res;
if (value == 0) return 0;
if (value > 0) return 1;
return -1;
}
接着往下看:
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;
}
ok ,就是这里了,所以我们只要能改变metrics.density的值,那整个屏幕中所以空间的大小肯定也就跟着改变了,对不对。但是如何改变呢?
解决办法
我们来看下Configuration这个类:
public void updateConfiguration(Configuration config,
DisplayMetrics metrics, CompatibilityInfo compat) {
synchronized (mAccessLock) {
if (false) {
Slog.i(TAG, "**** Updating config of " + this + ": old config is "
+ mConfiguration + " old compat is " + mCompatibilityInfo);
Slog.i(TAG, "**** Updating config of " + this + ": new config is "
+ config + " new compat is " + compat);
}
if (compat != null) {
mCompatibilityInfo = compat;
}
if (metrics != null) {
mMetrics.setTo(metrics);
}
// NOTE: We should re-arrange this code to create a Display
// with the CompatibilityInfo that is used everywhere we deal
// with the display in relation to this app, rather than
// doing the conversion here. This impl should be okay because
// we make sure to return a compatible display in the places
// where there are public APIs to retrieve the display... but
// it would be cleaner and more maintainble to just be
// consistently dealing with a compatible display everywhere in
// the framework.
mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
final int configChanges = calcConfigChanges(config);
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
mConfiguration.setLayoutDirection(mConfiguration.locale);
}
// * 当当当,看这里,看这里,就是这里,所以 ,你懂的...
if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
mMetrics.densityDpi = mConfiguration.densityDpi;
mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
}
mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
String locale = null;
if (mConfiguration.locale != null) {
locale = adjustLanguageTag(mConfiguration.locale.toLanguageTag());
}
final int width, height;
if (mMetrics.widthPixels >= mMetrics.heightPixels) {
width = mMetrics.widthPixels;
height = mMetrics.heightPixels;
} else {
//noinspection SuspiciousNameCombination
width = mMetrics.heightPixels;
//noinspection SuspiciousNameCombination
height = mMetrics.widthPixels;
}
//无辜的省略号......
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
locale, mConfiguration.orientation,
mConfiguration.touchscreen,
mConfiguration.densityDpi, mConfiguration.keyboard,
keyboardHidden, mConfiguration.navigation, width, height,
mConfiguration.smallestScreenWidthDp,
mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
mConfiguration.screenLayout, mConfiguration.uiMode,
Build.VERSION.RESOURCES_SDK_INT);
//无辜的省略号......
}
so,顺利成章的,有了下面的方法:
public class ConfigurationDPIUtil {
public static void initDpi(Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
Configuration configuration = context.getResources().getConfiguration();
configuration.densityDpi = DisplayMetrics.DENSITY_XHIGH ;//densityDpi 值越大,那显示时 dp对应的pix就越大
context.getResources().updateConfiguration(configuration,displayMetrics);
}
}
总结
做事情,要有思路,没有思路是因为自己的知识框架没有搭建好,是自己的实践和总结不到位。