Android常用度量单位:
- px:是Pixel的缩写,也就是说像素
- inch:是指英寸,设备对角线的长度
- dpi:它表示每英寸上的像素点个数,也就是屏幕密度。例如手机分辨率为1920*1080,先利用勾股定理得其对角线的像素值为2202.91,再除以对角线的长度5,计算出440.582便是该设备的屏幕密度dpi。
- dp:android中常用的使用单位,不要与dpi混淆,下面会详细介绍dp
ldpi、mdpi、hdpi、xhdpi、xxhdpi 这在android开发中非常常见,android根据dpi把设备分成多个级别,详细关系见下图:
可以通过代码获取设备dpi:
Resources resources=getResources();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
float density = displayMetrics.density;
int dpi = displayMetrics.densityDpi;
假设当前设备dpi为480,density就为3。density是一个倍数关系,是当前设备dpi除以160所得。当设备dpi为160时,1px=1dp,所以dpi为480时 1dp=3px
图片加载:
通常为了适配以及防止图片失真,会在mdpi,xhdpi,xxhdpi这类文件夹下分别放入对应的尺寸的图。那么为什么需要这样放,如果把本应该放入xxhdpi的图放入mdpi会发生什么呢
经过测试会发现 假设你的手机显示类别为xxhdpi,图片尺寸100*100.把它放入xxhdpi文件中,然后通过下面代码获取图片宽高:
BitmapDrawable bitmapDrawable = (BitmapDrawable) imageview.getDrawable();
if (null != bitmapDrawable) {
Bitmap bitmap = bitmapDrawable.getBitmap();
int width = bitmap.getWidth();
int height = bitmap.getHeight();
}
打印宽高会发现和图片原始尺寸一下是100*100.然后把图片移到mdpi中,运行后会发现图片被放大了3倍,变成了300*300。
从图片加载源码中找找关系,首先来看BitmapFactory中的的decodeResource()方法:
public static Bitmap decodeResource(Resources res, int id, Options opts) {
Bitmap bm = null;
InputStream is = null;
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);
bm = decodeResourceStream(res, value, is, null, opts);
} catch (Exception e) {
} finally {
try {
if (is != null) is.close();
} catch (IOException e) {
}
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
return bm;
}
typevalue保存了和当前设备dpi最接近的图片所在文件夹的destinydpi。比如图片在xxhdpi中 desinydpi就是480.接着看decodeResourceStream方法:
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
value.density被赋值给opts.inDensity,保存了图片所在文件夹的dpi 如果放在xxhdpi中 那就是480
opts.inTargetDensity = res.getDisplayMetrics().densityDpi 可以看出inTargetDensity保存的当前设备的dpi。
接着往下看decodeStream方法:
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
// we don't throw in this case, thus allowing the caller to only check
// the cache, and not force the image to be decoded.
if (is == null) {
return null;
}
Bitmap bm = null;
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
try {
if (is instanceof AssetManager.AssetInputStream) {
final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
bm = nativeDecodeAsset(asset, outPadding, opts);
} else {
bm = decodeStreamInternal(is, outPadding, opts);
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
setDensityFromOptions(bm, opts);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}
return bm;
}
在该方法中会调用decodeStreamInternal();它又会继续调用nativeDecodeStream( ),该方法是native的;在BitmapFactory.cpp可见这个方法内部又调用了doDecode()它的核心源码如下:
static jobject doDecode(JNIEnv*env,SkStreamRewindable*stream,jobject padding,jobject options) {
......
if (density != 0 && targetDensity != 0 && density != screenDensity) {
scale = (float) targetDensity / density;
}
......
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
scaledWidth = int(scaledWidth * scale + 0.5f);
scaledHeight = int(scaledHeight * scale + 0.5f);
}
if (willScale) {
const float sx = scaledWidth / float(decodingBitmap.width());
const float sy = scaledHeight / float(decodingBitmap.height());
......
SkPaint paint;
SkCanvas canvas(*outputBitmap);
canvas.scale(sx, sy);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
}
......
}
可以看到缩放比scale就等于opts.inTargetDensity/opts.inDensity,也就是屏幕dpi除以图片所在文件夹dpi,所以如果把原本在xxhdpi(480)文件夹中图片误放到mdpi(160)中 opts.inTargetDensity/opts.inDensity值就会增大,所以图片被放大到3倍。
正因为会自动选择缩放图片 我们只在xxhdpi中放了一套图 使用mdpi的手机来显示图片就会自动把图片缩小。所以现在很多开发者为了缩小apk大小会选择只在最大众的文件夹中放入一套图 比如xxhdpi。
如果不想让图片被缩放 可以试试把图片放到drawable-nodpi文件夹中哦。