Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Drawable適配

Drawable適配

編輯:關於Android編程

一直都是在自家的盒子上開發App,很少接觸到Android適配的問題。但是不得不說Android嚴重的碎片化,對於應用開發者來說,學會Android適配的是必要的。意識到自己就得不足就馬上行動,而Android適配的問題太多,有屏幕尺寸的適配、屏幕分辨率的適配以及android不同系統版本的適配。反映在代碼上來說,就是需要在資源文件上面下功夫,主要是layout和drawable文件目錄下的文件,這裡主要就研究一下drawable的適配。

Q:Android項目中那麼多drawable(mdpi、hdpi、xhdpi、xxhdpi等),那應用的配圖應該放在哪個目錄之下呢?
這個問題先不解答,先了解一下Android適配的基本知識。

Android適配的基本知識

Android中的長度單位

Android中與適配有關的主要的長度單位有dp、dip、dpi、sp、px。下面一一解釋一下。

px(pixel)

表示屏幕實際的像素。例如,1200×1920的屏幕在橫向有1200個像素,在縱向有1920個像素。

dpi(dot per inch)

表示屏幕密度是指每英寸上的像素點數。Android將根據不同的dpi將Android設備分成多個顯示級別。具體如下:

這裡寫圖片描述

 <喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPtX9yOdkcmF3YWJsZcS/wry6zW1pcG1hcMS/wrzT0GxkcGmhom1kcGmhomhkcGmhonhoZHBpoaJ4eGhkcGnWrrfWoaM8L3A+DQo8cD48ZW0+1eLA773iys3Su8/CbWlwbWFwus1kcmF3YWJsZbXEx/ix8DwvZW0+PGJyIC8+DQrU2kFuZHJvaWQgc3R1ZGlvv6q3otbQo6zQwr2o0ru49m1vZHVsZbXEyrG68rK7zazT60VjbGlwc2Uou+HJ+rPJZHJhd2FibGWhomRyYXdhYmxlLWxkcGmhomRyYXdhYmxlLW1kcGmhomRyYXdhYmxlLWh4cGm1yLXIKaOs1NrXytS0zsS8/tbQu+HJ+rPJbWlwbWFwLWhkcGmhom1pcG1hcC1tZHBpoaJtaXBtYXAteGhkcGmhom1pcG1hcC14eGhkcGm6zdK7uPZkcmF3YWJsZcS/wryhozxiciAvPg0KUaO61eK49tK7z7XB0LXEbWlwbWFwzsS8/rzQysfTw8C0uMnJtrXExNijvzxiciAvPg0KQaO6tPK/qsv509DS1G1pcG1hcL+qzbe1xM7EvP680Leiz9bA78PmtcTOxLz+trzKx2ljX2xhdW5jaGVyLnBuZ6GjPGJyIC8+DQpRo7ppY19sYXVuY2hlci5wbmfS1MewtrzKx9Ta0tRkcmF3YWJsZb+qzbe1xMS/wrzW0KOsz9bU2r2raWNfbGF1bmNoZXIucG5nt8XU2tLUbWlwbWFwv6rNt7XEzsS8/rzQo6zKx7K7ysfS1LrztcS/qreisrvQ6NKq0tRkcmF3YWJsZb+qzbe1xMS/wrzBy6GjPGJyIC8+DQpBo7q21NPa1eK49s7KzOK5yLjoudm3vdPQy7W3qDxiciAvPg0KZHJhd2FibGUvPGJyIC8+DQpGb3IgYml0bWFwIGZpbGVzIChQTkcsIEpQRUcsIG9yIEdJRiksIDktUGF0Y2ggaW1hZ2UgZmlsZXMsIGFuZCBYTUwgZmlsZXMgdGhhdCBkZXNjcmliZSBEcmF3YWJsZSBzaGFwZXMgb3IgRHJhd2FibGUgb2JqZWN0cyB0aGF0IGNvbnRhaW4gbXVsdGlwbGUgc3RhdGVzIChub3JtYWwsIHByZXNzZWQsIG9yIGZvY3VzZWQpLjxiciAvPg0K0uLLvMrHy7XS1GRyYXdhYmxlv6rNt7XExL/CvLTmt8W1xM7EvP7T0HBuZ6GianBlZ6GiZ2lmuPHKvc28xqzOxLz+oaIuOc28xqzS1Lyw0rvQqVhNTM7EvP6hozxiciAvPg0KbWlwbWFwLzxiciAvPg0KRm9yIGFwcCBsYXVuY2hlciBpY29ucy48YnIgLz4NCrb40tRtaXBtYXC/qs23tcTEv8K8tOa3xbXEysdBcHC1xM28seqhozwvcD4NCjxzdHJvbmc+ZHA8L3N0cm9uZz4NCjxwPtKyvdBkaXAoZGVuc2l0eSBpbmRlcGVuZGVudCBwaXhlbCnWsdLrzqrD3LbIzt652LXEz/HL2KGjztLDx7LCsuLI57n7yrnTw8HL1eK49rWlzrujrM7Sw8fU2rK7zazGwcS7w9y2yLXEyeixuMnPz9TKvrXEs6S2yL7Nu+HKx8/gzay1xKGjzsrM4sC0wcujrNTaxsHEu8nPz9TKvs28z/HKsba8ysfU2sbBxLvJz8zus+TP8cvYteOjrLb4yrnTw9Xi1tbT68PctsjO3rnYtcTP8cvYo6jO0sPH1NqyvL7WzsS8/tbQyrnTw7XEIGRwL2RpcCC+zcrH0+vD3LbIzt652LXEz/HL2KOpysfI57rO16q7u7PJz/HL2LXExNijvzxiciAvPg0KxuTKtdTasLLXv9bQo6y9q8bBxLvD3LbIzqoxNjBkcGm1xNbQw9y2yMnosbjGwcS71/fOqrv517zGwcS7o6zU2tXiuPbGwcS71tCjrDFkcD0xcHiho8bky/vGwcS7w9y2yLXEyeixuLC01dWxyMD9u7vL46Osvt/M5cjnz8Kx7aO6PC9wPg0KPHA+Jm5ic3A7PC9wPg0KPGNlbnRlcj4NCgk8aW1nIGFsdD0="這裡寫圖片描述" src="/uploadfile/Collfiles/20160504/20160504090158189.png" title="\" />

 

由上表不難計算1dp在hdpi設備下等於1.5px,同樣的在xxhdpi設備下1dp=3px。這裡我們從dp到px解釋了Android中不同屏幕密度之間的像素比例關系。
下面換一個角度,從px到dp的變化來說明這種比例關系。
就拿為App設計icon來說,為了讓App的icon在不同的屏幕密度設備顯示相同(這裡我們選擇在以mipmap開頭的目錄中都要設計一個icon),就必須要icon在屏幕中占據相同的dp。那麼對於不同的屏幕密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)應按照 2:3:4:6:8 的比例進行縮放。比如說一個icon的尺寸為48x48dp,這表示在 MDPI 的屏幕上其實際尺寸應為 48x48px,在 HDPI 的屏幕上其實際大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的屏幕上其實際大小是 MDPI 的 2 倍 (96x96 px),依此類推。

備注:
圖片的描述有兩種:
1、僅僅通過寬高的像素。
2、通過圖片分辨率(不同於屏幕分辨率,單位英寸中所包含的像素點數)和尺寸大小。

sp

sp,即scale-independent pixels,與dp類似,但是可以在設置裡面調節字號的時候,文字會隨之改變。當安卓系統字號設為“普通”時,sp與px的尺寸換算和dp與px是一樣的。

屏幕尺寸、屏幕分辨率、屏幕密度

屏幕尺寸

設備的物理屏幕尺寸,指屏幕的對角線的長度,單位是英寸,1 inch = 2.54 cm。比如某某手機為“5寸大屏手機”,就是指對角線的尺寸,5寸×2.54厘米/寸=12.7厘米。

屏幕分辨率

也叫顯示分辨率,是屏幕圖像的精密度,是指屏幕或者顯示器所能顯示的像素點有多少。一般以橫向像素×縱向像素表示分辨率,如1200×1920表示此屏幕在寬度方向有1200個像素,在高度方向有1920個像素。

屏幕密度

屏幕密度是指每英寸上的像素點數,單位是dpi(dot per inch)或者ppi(pixels per inch)。顧名思義,就是每英寸的像素點數,數值越高當然顯示越細膩。屏幕密度與屏幕尺寸和屏幕分辨率有關。例如在屏幕尺寸一定的條件下,屏幕分辨率越高屏幕密度越大,反之越小。同理在屏幕分辨率一定的條件下,屏幕尺寸越小屏幕密度越大,反之越小。

屏幕密度的計算
不同於屏幕尺寸個屏幕分辨率,這兩個值是可以直接得到的。屏幕密度需要我們計算得到。例如我的手機的分辨率是1200×1920,屏幕尺寸是5寸的。根據屏幕尺寸、屏幕分辨率和屏幕密度定義不難看出他們之間的關系如下圖:

這裡寫圖片描述

這樣根據勾股定理,我們得出對角線的像素數大約是2264,那麼用2264除以7就是此屏幕的密度了,計算結果是323。
備注:
在Android中,上面的出現的0.75,1,1.5,2,3,4才是屏幕密度(density)。而120,160,240,320,480,640是屏幕密度dpi(densityDpi)。

 

實際密度與系統密度

實際密度就是我們自己算出來的密度,這個密度代表了屏幕真實的細膩程度,如上述例子中的323dpi就是實際密度,說明這塊屏幕每寸有323個像素。
7英寸1200×1920的屏幕密度是323,5英寸1200×1920的屏幕密度是452,而相同分辨率的4.5英寸屏幕密度是503。如此看來,屏幕密度將會出現很多數值,呈現嚴重的碎片化。而密度又是安卓屏幕將界面進行縮放顯示的依據,那麼安卓是如何適配這麼多屏幕的呢?
其實,每部安卓手機屏幕都有一個初始的固定密度,這些數值是120、160、240、320、480,這些就是android為不同設備設定的系統密度。
得到實際密度以後,一般會選擇一個最近的密度作為系統密度,系統密度是出廠預置的,如440dpi的系統密度就是和它最接近的480dpi;如果是330dpi的設備,它的系統密度就是320dpi。但是,現在很多手機不一定會選擇這些值作為系統密度,而是選擇實際的dpi作為系統密度,這就導致了很多手機的dpi也不是在這些值內。例如小米Note這樣的xxhdpi的設備他的系統密度並不是480,而是它的實際密度440。

獲取設備的上述屬性

Android系統中有個DisplayMetrics的類,通過這個類就可以得到上述的所有屬性。

        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        float density = displayMetrics.density; //屏幕密度
        int densityDpi = displayMetrics.densityDpi;//屏幕密度dpi
        int heightPixels = displayMetrics.heightPixels;//屏幕高度的像素
        int widthPixels = displayMetrics.widthPixels;//屏幕寬度的像素
        float scaledDensity = displayMetrics.scaledDensity;//字體的放大系數
        float xdpi = displayMetrics.xdpi;//寬度方向上的dpi
        float ydpi = displayMetrics.ydpi;//高度方向上的dpi
        Log.i(TAG, "density = " + density);
        Log.i(TAG, "densityDpi = " + densityDpi);
        Log.i(TAG, "scaledDensity = " + scaledDensity);
        Log.i(TAG, "Screen resolution = " + widthPixels + "×" + heightPixels);
        Log.i(TAG, "xdpi = " + xdpi);
        Log.i(TAG, "ydpi = " + ydpi);

其中xdpi=ydpi=densityDpi.打印結果如下:

這裡寫圖片描述

上面計算的我的設備的dpi為323。這裡系統給定的屏幕密度dpi為320。

 

OK,基礎的東西介紹完了,那就回答一下上面的問題吧,android項目中那麼多以drawable開頭的文件夾,那應用的配圖應該放在哪個文件夾之下呢?
A:一般的開發的話,都會做三套配圖,對應著drawable-hdpi、drawable-xhdpi、drawable-xxhdpi三個文件夾。
Q:那要是做一套呢?應該做哪一套呢?
A:做一套也是可以的,放在drawable-xxdpi文件夾中。
Q:為什麼做一套配圖要放在drawable-xxdpi文件夾中?
這裡先不給出答案,我在網上也看了一些說法,說是省內存,這裡暫且不置可否。我們就從內存的角度去看看是不是對的。
我們先從表觀上看看,同一張圖片,放置在不同的drawable文件夾,在同一設備上運行,對圖片大小及內存占用有什麼影響。

占用內存測試

首先我准備了一張600×960像素的png圖片大小為248k,文件名為test:

這裡寫圖片描述

給出測試代碼:
布局文件:

 



    

Activity的代碼:

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    ImageView imageView;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.imageView);
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        float density = displayMetrics.density; //屏幕密度
        int densityDpi = displayMetrics.densityDpi;//屏幕密度dpi
        int heightPixels = displayMetrics.heightPixels;//屏幕高度的像素
        int widthPixels = displayMetrics.widthPixels;//屏幕寬度的像素
        float scaledDensity = displayMetrics.scaledDensity;//字體的放大系數
        float xdpi = displayMetrics.xdpi;//寬度方向上的dpi
        float ydpi = displayMetrics.ydpi;//高度方向上的dpi
        Log.i(TAG, "density = " + density);
        Log.i(TAG, "densityDpi = " + densityDpi);
        Log.i(TAG, "scaledDensity = " + scaledDensity);
        Log.i(TAG, "Screen resolution = " + widthPixels + "×" + heightPixels);
        Log.i(TAG, "xdpi = " + xdpi);
        Log.i(TAG, "ydpi = " + ydpi);
        imageView.post(new Runnable() {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
            @Override
            public void run() {
                BitmapDrawable drawable = (BitmapDrawable) imageView.getDrawable();
                if (drawable != null) {
                    Bitmap bitmap = drawable.getBitmap();
                    Log.i(TAG, "bitmap width = " + bitmap.getWidth() + " bitmap height = " + bitmap.getHeight());
                    Log.i(TAG, "bitmap size = " + bitmap.getByteCount());//獲取bitmap的占用內存
                    Log.i(TAG, "imageView width = " + imageView.getWidth() + " imageView height = " + imageView.getHeight());
                    Log.i(TAG, "imageView scaleType = " + imageView.getScaleType());
                }
            }
        });
    }
}

將test.png放在drawable-xxhdpi文件夾下的顯示與log:

這裡寫圖片描述
這裡寫圖片描述

 

將test.png放在drawable-xhdpi文件夾下的顯示與log:


這裡寫圖片描述
這裡寫圖片描述

 

將test.png放在drawable-hdpi文件夾下的顯示與log:


這裡寫圖片描述
這裡寫圖片描述

 

將test.png放在drawable-mdpi文件夾下的顯示與log:


這裡寫圖片描述
這裡寫圖片描述

 

將test.png放在drawable-ldpi文件夾下的顯示與log:


這裡寫圖片描述
這裡寫圖片描述

 

測試結果如下:

 

這裡寫圖片描述

 

從上面的測試結果(同一款手機上),我們可以得出如下結論:
1、同一張圖片,放在不同目錄下,會生成不同大小的Bitmap。Bitmap的長度和寬度越大,占用的內存就越大。
2、同一張圖片,放在不同的drawable目錄下(從drawable-lpdi到drawable-xxhpdi)在同一手機上占用的內存越來越小。
3、圖片在硬盤上占用的大小,與在內存中占用的大小完全不一樣。

一個一個解釋,先說結論一。

為什麼同一張圖片,放在不同目錄下,會生成不同大小的Bitmap。

要想回答這個問題,必須要深入理解Android系統加載drawable目錄下圖片的過程。我們讀取的是 drawable 目錄下面的圖片,用的是 decodeResource 方法,

 final TypedValue value = new TypedValue();
            is = res.openRawResource(id, value);

            bm = decodeResourceStream(res, value, is, null, opts);

該方法本質上就兩步:

1、讀取原始資源,這個調用了 Resource.openRawResource 方法,這個方法調用完成之後會對 TypedValue 進行賦值,其中包含了原始資源的 density 等信息;原始資源的 density 其實取決於資源存放的目錄(比如 drawable-xxhdpi 對應的是480, drawable-hdpi對應的就是240,而drawable目錄對應的是TypedValue.DENSITY_DEFAULT=0)
2、調用 decodeResourceStream 對原始資源進行解碼和適配。這個過程實際上就是原始資源的 density 到屏幕 density 的一個映射。

 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);
    }

該方法主要就是對opts對象中的屬性進行賦值,代碼不難理解。如果value.density=DisplayMetrics.DENSITY_DEFAULT也就是0的話,將 opts.inDensity賦值為 DisplayMetrics.DENSITY_DEFAULT默認值為160.否則就將 opts.inDensity賦值為第一步獲取到的值。此外將 opts.inTargetDensity賦值為屏幕密度Dpi。inDensity 和 inTargetDensity要特別注意,這兩個值與下面 cpp 文件裡面的 density 和 targetDensity 相對應。

BitmapFactory.cpp

static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
......
    if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
        const int density = env->GetIntField(options, gOptions_densityFieldID);//通過JNI獲取opts.inDensity的值
        const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);//通過JNI獲取opts.inTargetDensity的值
        const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
        if (density != 0 && targetDensity != 0 && density != screenDensity) {
            scale = (float) targetDensity / density;//求出縮放的倍數。
        }
    }
}
const bool willScale = scale != 1.0f;
......
SkBitmap decodingBitmap;
if (!decoder->decode(stream, &decodingBitmap, prefColorType,decodeMode)) {
   return nullObjectReturn("decoder->decode returned false");
}
//這裡這個deodingBitmap就是解碼出來的bitmap,大小是圖片原始的大小
int scaledWidth = decodingBitmap.width();
int scaledHeight = decodingBitmap.height();
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);//畫出圖像
}
......
}

代碼中的density 和 targetDensity均是通過JNI獲取的值,前者是 opts.inDensity,targetDensity 實際上是opts.inTargetDensity也就是 DisplayMetrics 的 densityDpi,我的手機的densityDpi在上面已經打印過了320。最終我們看到 Canvas 放大了 scale 倍,然後又把讀到內存的這張 bitmap 畫上去,相當於把這張 bitmap 放大了 scale 倍。

Android中一張圖片(BitMap)占用的內存主要和以下幾個因數有關:圖片長度,圖片寬度,單位像素占用的字節數。這裡我們需要知道bitmap中單位像素占據的內存大小,而單位像素占據的內存大小是與.Options的inPreferredConfig有關的。

 public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;

inPreferredConfig的類型為Bitmap.Config默認值為Bitmap.Config.ARGB_8888。ARGB指的是一種色彩模式,裡面A代表Alpha,R表示red,G表示green,B表示blue。ARGB_8888代表的就是這四個通道各占8位也就是一個字節,合起來就是4個字節。同理Bitmap.Config中還有ARGB_4444、 ALPHA_8、 RGB_565 ,他們占用內存的大小和ARGB_8888一樣。

我們再來分析一下上面的那張表:

 

這裡寫圖片描述

 

已知圖片的大小為600×960,格式為png,測試手機的densityDpi為320( opts.inTargetDensity=320)。

當圖片放在drawable-mdpi目錄下時,此時得到的opts.inDensity=160,那麼放大的倍數就是320/160=2,放大後圖片的大小就是1200×1920,占用的內存就是:
1200×1920×4=9216000B,9216000÷1024÷1024≈8.79M.
同樣的當圖片放在drawable-xxhdpi目錄下時,此時得到的opts.inDensity=480,那麼放大的倍數就是320/480=2/3,放大後圖片的大小就是400×640,占用的內存就是:
400×640×4=1024000B,1024000÷1024÷1024≈0.98M.
其他的類似,這裡就不再贅述。至此就解釋了結論一。

解釋結論二之前,先說說結論三。

圖片在硬盤的大小和內存中的大小不一樣

解釋這個問題前,先說一個我曾經遇到的非技術問題:App為什麼不建議使用jpg圖片,因為同樣的尺寸,png格式的圖片要比jpg圖片大很多。(png中有透明通道,而jpg中沒有,此外png是無損壓縮的,而jpg是有損壓縮的,所以png中存儲的信息會很多,體積自然就大了)。
我們從三方面來談談這個問題。
1、從內存角度
不管是png還是jpg文件,他們都說的是文件存儲范疇的事情,它們只存在於文件系統,而非內存或者顯存。而前面也說過了,圖片占有的內存只與圖片長度、圖片寬度以及單位像素占用的字節數有關。所以jpg 格式的圖片與 png 格式的圖片在內存當中不應該有什麼不同。(這裡變相的也解釋了結論二:圖片在硬盤的大小和內存中的大小不一樣)。
Q:那既然內存占有不會有什麼不同,那為什麼開發者喜歡用png的圖片呢?
這個問題只能從另一個角度來說。
2、解碼速度
png是無損壓縮的,而jpg是有損壓縮的。所以png圖片的解碼速度明顯會高於jpg圖片,所以png雖然體積比jpg大但是加載速度卻要快一些。
Q:那麼png圖片有這種好處,那麼就把App中所有的圖片都換成png的吧?
這樣其實也不科學。為什麼呢,再換一個角度來分析
3、App包的大小以及流量的角度。
前面說過同樣的尺寸,png格式的圖片要比jpg圖片大很多。那打包出來的App自然很大,用戶的流量就會耗費很大。同時如果App中所有的圖片都換成png,那些下載的圖片(也全部變成png格式的)同樣又會有流量的問題。

綜上所述,android中使用png和jpg圖片各有優劣,我曾在網上看過有人為了減小App的大小,將所有的png圖都換成了jpg圖片。當然這也不是不可以,看你追求的是什麼。

是時候解釋結論二啦!

如果只做一套配圖,為什麼要放在drawable-xxdpi文件夾中。

結論二:同一張圖片,放在不同的drawable目錄下(從drawable-lpdi到drawable-xxhpdi)在同一手機上占用的內存越來越小。
其實它的解釋表觀上從測試結果的表中很明顯的體現出來了,並且從結論一的解釋中也能分析出為什麼從drawable-lpdi到drawable-xxhpdi占用內存越來越小。
解釋一下:同一部手機,它的densityDpi是固定的,而不同的drawable目錄對應的原始密度不同,並且從drawable-lpdi到drawable-xxhpdi原始密度越來越大,而圖片的放大倍數=densityDpi÷原始密度,所以放大倍數變小,自然占用的內存小了。

其實這裡想通過結論二解釋一下:只做一套配圖要放在drawable-xxdpi文件夾中是真的省內存嗎,進而回答如果只做一套配圖為什麼要放在drawable-xxdpi文件夾中。
Q:只做一套配圖要放在drawable-xxdpi文件夾中是真的省內存嗎?
A:這個問題就從內存分析,圖片占用的內存主要和以下幾個因數有關:
1、圖片在內存中的像素。
2、單位像素占用的字節數。
而對於同一應用來說,單位像素占用的字節數一定是相同的,那麼圖片占用的內存只與圖片在內存中的像素有關了,而圖片在內存中的像素又由圖片的原始像素和圖片在內存中放大的倍數(scale = densityDpi÷原始密度)。 綜上所述,圖片占用的內存和以下幾個因素有關:

圖片的原始像素。 設備的densityDpi 圖片在哪個drawable目錄下

設想一下,做一套適配xhdpi設備的配圖,放在drawable-xhdpi目錄下,和做一套適配mdpi設備的配圖,放在drawable-mdpi目錄下,這時候我用一個hdpi的設備來測試這兩種方案,哪種方案更省內存呢?
咱們具體分析一下,就拿我的設備來說是xhdpi的,分辨率為1200×1920,仍舊用原來的測試代碼,要讓imagevie顯示為全屏,並且圖片放在drawable-xhdpi目錄下,那麼圖片的原始像素就應該是1200×1920。對於相同尺寸的mdpi來說,他的分辨率是600×960,要讓imagevie顯示為全屏,並且圖片放在drawable-mhdpi目錄下,那麼圖片的原始像素就應該是600×960。
這時候使用hdpi的設備來測試方案一,可以得到:
圖片原始的像素為1200×1920,設備的densityDpi為240,原始的dpi為320。所以放大倍數為2/3,最終圖片在內存中的大小為800×1280。
使用hdpi的設備來測試方案二,可以得到:
圖片原始的像素為600×960,設備的densityDpi為240,原始的dpi為160。所以放大倍數為3/2,最終圖片在內存中的大小也為800×1280。
當然在其他不同dpi的設備上這兩種方案占用的內存也是一樣的,這裡就不再贅述。

所以如果只做一套圖的話,不考慮app包的大小以及app內部配圖的清晰度來說,只要圖片所處的drawable目錄適配該目錄對應著dpi設備(例如做一套適配mdpi設備的圖,將這些配圖放在drawable-mdpi目錄下),再通過android系統加載圖片的縮放機制後,不論哪種方案,對於同種dpi的設備,圖片所占的內存是相同的。

但是這些都是不考慮app包的大小以及app內部配圖的清晰度來說的,事實上不可能不考慮這些因素。在考慮這些因素之前,先說一下圖片縮放的問題。
圖片的像素、分辨率以及尺寸滿足如下關系:

分辨率 = 像素 ÷ 尺寸

圖片在進行縮放的時候,分辨率是不變的,變化的是像素和尺寸。比如將圖片放大兩倍,這時就會有白色像素插值補充原有的像素中,所以就會看起來模糊,清晰度不高。而縮小圖片,會通過對圖片像素采樣生成縮略圖,將會增強它的平滑度和清晰度。

 

如果制作適配xxhdpi設備的圖片,同時放在drawable-xxhdpi目錄中,其他除了xxxdpi的設備在顯示配圖時都是縮小原圖,不會出現模糊的情況。而這樣的話App打包由於配圖的質量高,自然App會相對大些。
如果制作適配mdpi設備的圖片,同時放在drawable-mdpi目錄中,其他除了ldpi的設備在顯示配圖時都是放大原圖,dpi越大的設備顯示的配圖也就越模糊。
相比較App的大小和用戶體驗來說,毫無疑問,用戶至上。所以如果只有一套配圖的話,制作高清大圖適配xxdpi的設備,將配圖放置在drawable-xxhdpi目錄下就可以了。
Q:為什麼不制作適配xxxdpi設備的配圖,放在drawable-xxxhdpi目錄下?
A:市場上xxxdpi的設備不多,沒必要為了那麼一點點特殊群體來加大app的容量(4÷3≈1.3倍,容量放大的倍數不小呀!!!)

好了,所有的問題都解釋過了,不過細心的同學可能會發現


這裡寫圖片描述
這裡寫圖片描述
將test.png放在drawable-ldpi文件夾下的顯示與log

log中位圖的大小1600×2560,而imageView的大小為1200×1920,但是顯示的仍舊是全圖,這是因為imageview有個屬性是ScaleType。該屬性用來表示imageview顯示圖片的方式,一共有8種取值:
1. ScaleType.CENTER:圖片大小為原始大小,如果圖片大小大於ImageView控件,則截取圖片中間部分,若小於,則直接將圖片居中顯示。
2. ScaleType.CENTER_CROP:將圖片等比例縮放,讓圖像的短邊與ImageView的邊長度相同,即不能留有空白,縮放後截取中間部分進行顯示。
3. ScaleType.CENTER_INSIDE:將圖片大小大於ImageView的圖片進行等比例縮小,直到整幅圖能夠居中顯示在ImageView中,小於ImageView的圖片不變,直接居中顯示。
4. ScaleType.FIT_CENTER:ImageView的默認狀態,大圖等比例縮小,使整幅圖能夠居中顯示在ImageView中,小圖等比例放大,同樣要整體居中顯示在ImageView中。
5. ScaleType.FIT_END:縮放方式同FIT_CENTER,只是將圖片顯示在右方或下方,而不是居中。
6. ScaleType.FIT_START:縮放方式同FIT_CENTER,只是將圖片顯示在左方或上方,而不是居中。
7. ScaleType.FIT_XY:將圖片非等比例縮放到大小與ImageView相同。
8. ScaleType.MATRIX:是根據一個3x3的矩陣對其中圖片進行縮放。
ImageView的默認狀態為ScaleType.FIT_CENTER,所以bitmap又縮小至imageview的大小,仍舊可以全圖顯示。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved