Android高效安全加載圖片的方法詳解
1. 概述
在 Android 應(yīng)用程序的設(shè)計(jì)中,幾乎不可避免地都需要加載和顯示圖片,由于不同的圖片在大小上千差萬(wàn)別,有些圖片可能只需要幾十KB的內(nèi)存空間,有些圖片卻需要占用幾十MB的內(nèi)存空間;或者一張圖片不需要占用太多的內(nèi)存,但是需要同時(shí)加載和顯示多張圖片。
在這些情況下,加載圖片都需要占用大量的內(nèi)存,而 Android系統(tǒng)分配給每個(gè)進(jìn)程的內(nèi)存空間是有限的,如果加載的圖片所需要的內(nèi)存超過(guò)了限制,進(jìn)程就會(huì)出現(xiàn) OOM,即內(nèi)存溢出。
本文針對(duì)加載大圖片或者一次加載多張圖片等兩種不同的場(chǎng)景,采用不同的加載方式,以盡量避免可能導(dǎo)致的內(nèi)存溢出問(wèn)題。
下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧
2. 加載大圖片
有時(shí)一張圖片的加載和顯示就需要占用大量的內(nèi)存,例如圖片的大小是 2592x1936 ,同時(shí)采用的位圖配置是 ARGB_8888 ,其在內(nèi)存中需要的大小是 2592x1936x4字節(jié),大概是 19MB。僅僅加載這樣一張圖片就可能會(huì)超過(guò)進(jìn)程的內(nèi)存限制,進(jìn)而導(dǎo)致內(nèi)存溢出,所以在實(shí)際使用時(shí)肯定無(wú)法直接加載到內(nèi)存中。
為了避免內(nèi)存溢出,根據(jù)不同的顯示需求,采取不同的加載方式:
- 顯示一張圖片的全部?jī)?nèi)容:對(duì)原圖片進(jìn)行 壓縮顯示。
- 顯示一張圖片的部分內(nèi)容:對(duì)原圖片進(jìn)行 局部顯示。
2.1 圖片壓縮顯示
圖片的壓縮顯示指的是對(duì)原圖片進(jìn)行長(zhǎng)寬的壓縮,以減少圖片的內(nèi)存占用,使其能夠在應(yīng)用上正常顯示,同時(shí)保證在加載和顯示過(guò)程中不會(huì)出現(xiàn)內(nèi)存溢出的情況。
BitmapFactory 是一個(gè)創(chuàng)建Bitmap 對(duì)象的工具類,使用它可以利用不同來(lái)源的數(shù)據(jù)生成Bitamp對(duì)象,在創(chuàng)建過(guò)的過(guò)程中還可以對(duì)需要生成的對(duì)象進(jìn)行不同的配置和控制,BitmapFactory的類聲明如下:
Creates Bitmap objects from various sources, including files, streams,and byte-arrays.
由于在加載圖片前,是無(wú)法提前預(yù)知圖片大小的,所以在實(shí)際加載前必須根據(jù)圖片的大小和當(dāng)前進(jìn)程的內(nèi)存情況來(lái)決定是否需要對(duì)圖片進(jìn)行壓縮,如果加載原圖片所需的內(nèi)存空間已經(jīng)超過(guò)了進(jìn)程打算提供或可以提供的內(nèi)存大小,就必須考慮壓縮圖片。
2.1.1 確定原圖片長(zhǎng)寬
簡(jiǎn)單來(lái)說(shuō),壓縮圖片就是對(duì)原圖的長(zhǎng)寬按照一定的比例進(jìn)行縮小,所以首先要確定原圖的長(zhǎng)寬信息。為了獲得圖片的長(zhǎng)寬信息,利用 BitmapFactory.decodeResource(Resources res, int id, Options opts) 接口,其聲明如下:
/**
* Synonym for opening the given resource and calling
* {@link #decodeResourceStream}.
*
* @param res The resources object containing the image data
* @param id The resource id of the image data
* @param opts null-ok; Options that control downsampling and whether the
* image should be completely decoded, or just is size returned.
* @return The decoded bitmap, or null if the image data could not be
* decoded, or, if opts is non-null, if opts requested only the
* size be returned (in opts.outWidth and opts.outHeight)
* @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig}
* is {@link android.graphics.Bitmap.Config#HARDWARE}
* and {@link BitmapFactory.Options#inMutable} is set, if the specified color space
* is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
* function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
*/
public static Bitmap decodeResource(Resources res, int id, Options opts) {
通過(guò)這個(gè)函數(shù)聲明,可以看到通過(guò)這個(gè)接口可以得到圖片的長(zhǎng)寬信息,同時(shí)由于返回 null并不申請(qǐng)內(nèi)存空間,避免了不必要的內(nèi)存申請(qǐng)。
為了得到圖片的長(zhǎng)寬信息,必須傳遞一個(gè) Options 參數(shù),其中的 inJustDecodeBounds 設(shè)置為 true,其聲明如下:
/** * If set to true, the decoder will return null (no bitmap), but * the <code>out...</code> fields will still be set, allowing the caller to * query the bitmap without having to allocate the memory for its pixels. */ public boolean inJustDecodeBounds;
下面給出得到圖片長(zhǎng)寬信息的示例代碼:
BitmapFactory.Options options = new BitmapFactory.Options(); // 指定在解析圖片文件時(shí),僅僅解析邊緣信息而不創(chuàng)建 bitmap 對(duì)象。 options.inJustDecodeBounds = true; // R.drawable.test 是使用的 2560x1920 的測(cè)試圖片資源文件。 BitmapFactory.decodeResource(getResources(), R.drawable.test, options); int width = options.outWidth; int height = options.outHeight; Log.i(TAG, "width: " + width + ", height: " + height);
在實(shí)際測(cè)試中,得到的長(zhǎng)寬信息如下:
01-05 04:06:23.022 29836 29836 I Android_Test: width: 2560, height: 1920
2.1.2 確定目標(biāo)壓縮比例
得知原圖片的長(zhǎng)寬信息后,為了能夠進(jìn)行后續(xù)的壓縮操作,必須要先確定目標(biāo)壓縮比例。所謂壓縮比例就是指要對(duì)原始的長(zhǎng)寬進(jìn)行的裁剪比例,如果如果原圖片是 2560x1920,采取的壓縮比例是 4,進(jìn)行壓縮后的圖片是 640x480,最終大小是原圖片的1/16。
壓縮比例在 BitmapFactory.Options中對(duì)應(yīng)的屬性是 inSampleSize,其聲明如下:
/** * If set to a value > 1, requests the decoder to subsample the original * image, returning a smaller image to save memory. The sample size is * the number of pixels in either dimension that correspond to a single * pixel in the decoded bitmap. For example, inSampleSize == 4 returns * an image that is 1/4 the width/height of the original, and 1/16 the * number of pixels. Any value <= 1 is treated the same as 1. Note: the * decoder uses a final value based on powers of 2, any other value will * be rounded down to the nearest power of 2. */ public int inSampleSize;
需要特別注意的是,inSampleSize 只能是 2的冪,如果傳入的值不滿足條件,解碼器會(huì)選擇一個(gè)和傳入值最節(jié)儉的2的冪;如果傳入的值小于 1,解碼器會(huì)直接使用1。
要確定最終的壓縮比例,首先要確定目標(biāo)大小,即壓縮后的目標(biāo)圖片的長(zhǎng)寬信息,根據(jù)原始長(zhǎng)寬和目標(biāo)長(zhǎng)寬來(lái)選擇一個(gè)最合適的壓縮比例。下面給出示例代碼:
/**
* @param originWidth the width of the origin bitmap
* @param originHeight the height of the origin bitmap
* @param desWidth the max width of the desired bitmap
* @param desHeight the max height of the desired bitmap
* @return the optimal sample size to make sure the size of bitmap is not more than the desired.
*/
public static int calculateSampleSize(int originWidth, int originHeight, int desWidth, int desHeight) {
int sampleSize = 1;
int width = originWidth;
int height = originHeight;
while((width / sampleSize) > desWidth && (height / sampleSize) > desHeight) {
sampleSize *= 2;
}
return sampleSize;
}
需要注意的是這里的desWidth和desHeight 是目標(biāo)圖片的最大長(zhǎng)寬值,而不是最終的大小,因?yàn)橥ㄟ^(guò)這個(gè)方法確定的壓縮比例會(huì)保證最終的圖片長(zhǎng)寬不大于目標(biāo)值。
在實(shí)際測(cè)試中,把原圖片大小設(shè)置為2560x1920,把目標(biāo)圖片大小設(shè)置為100x100:
int sampleSize = BitmapCompressor.calculateSampleSize(2560, 1920, 100, 100); Log.i(TAG, "sampleSize: " + sampleSize);
測(cè)試結(jié)果如下:
01-05 04:42:07.752 8835 8835 I Android_Test: sampleSize: 32
最終得到的壓縮比例是32,如果使用這個(gè)比例去壓縮2560x1920的圖片,最終得到80x60的圖片。
2.1.3 壓縮圖片
在前面兩部分,分別確定了原圖片的長(zhǎng)寬信息和目標(biāo)壓縮比例,其實(shí)確定原圖片的長(zhǎng)寬也是為了得到壓縮比例,既然已經(jīng)得到的壓縮比較,就可以進(jìn)行實(shí)際的壓縮操作了,只需要把得到的inSampleSize通過(guò)Options傳遞給BitmapFactory.decodeResource(Resources res, int id, Options opts)即可。
下面是示例代碼:
public static Bitmap compressBitmapResource(Resources res, int resId, int inSampleSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
return BitmapFactory.decodeResource(res, resId, options);
}
2.2 圖片局部顯示
圖片壓縮會(huì)在一定程度上影響圖片質(zhì)量和顯示效果,在某些場(chǎng)景下并不可取,例如地圖顯示時(shí)要求必須是高質(zhì)量圖片,這時(shí)就不能進(jìn)行壓縮處理,在這種場(chǎng)景下其實(shí)并不要求要一次顯示圖片的所有部分,可以考慮一次只加載和顯示圖片的特定部分,即***局部顯示***。
要實(shí)現(xiàn)局部顯示的效果,可以使用BitmapRegionDecoder 來(lái)實(shí)現(xiàn),它就是用來(lái)對(duì)圖片的特定部分進(jìn)行顯示的,尤其是在原圖片特別大而無(wú)法一次全部加載到內(nèi)存的場(chǎng)景下,其聲明如下:
/**
* BitmapRegionDecoder can be used to decode a rectangle region from an image.
* BitmapRegionDecoder is particularly useful when an original image is large and
* you only need parts of the image.
*
* <p>To create a BitmapRegionDecoder, call newInstance(...).
* Given a BitmapRegionDecoder, users can call decodeRegion() repeatedly
* to get a decoded Bitmap of the specified region.
*
*/
public final class BitmapRegionDecoder { ... }
這里也說(shuō)明了如果使用BitmapRegionDecoder進(jìn)行局部顯示:首先通過(guò)newInstance()創(chuàng)建實(shí)例,再利用decodeRegion()對(duì)指定區(qū)域的圖片內(nèi)存創(chuàng)建Bitmap對(duì)象,進(jìn)而在顯示控件中顯示。
通
過(guò)BitmapRegionDecoder.newInstance()創(chuàng)建解析器實(shí)例,其函數(shù)聲明如下:
/**
* Create a BitmapRegionDecoder from an input stream.
* The stream's position will be where ever it was after the encoded data
* was read.
* Currently only the JPEG and PNG formats are supported.
*
* @param is The input stream that holds the raw data to be decoded into a
* BitmapRegionDecoder.
* @param isShareable If this is true, then the BitmapRegionDecoder may keep a
* shallow reference to the input. If this is false,
* then the BitmapRegionDecoder will explicitly make a copy of the
* input data, and keep that. Even if sharing is allowed,
* the implementation may still decide to make a deep
* copy of the input data. If an image is progressively encoded,
* allowing sharing may degrade the decoding speed.
* @return BitmapRegionDecoder, or null if the image data could not be decoded.
* @throws IOException if the image format is not supported or can not be decoded.
*
* <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT},
* if {@link InputStream#markSupported is.markSupported()} returns true,
* <code>is.mark(1024)</code> would be called. As of
* {@link android.os.Build.VERSION_CODES#KITKAT}, this is no longer the case.</p>
*/
public static BitmapRegionDecoder newInstance(InputStream is,
boolean isShareable) throws IOException { ... }
需要注意的是,這只是BitmapRegionDecoder其中一個(gè)newInstance函數(shù),除此之外還有其他的實(shí)現(xiàn)形式,讀者有興趣可以自己查閱。
在創(chuàng)建得到BitmapRegionDecoder實(shí)例后,可以調(diào)用decodeRegion方法來(lái)創(chuàng)建局部Bitmap對(duì)象,其函數(shù)聲明如下:
/**
* Decodes a rectangle region in the image specified by rect.
*
* @param rect The rectangle that specified the region to be decode.
* @param options null-ok; Options that control downsampling.
* inPurgeable is not supported.
* @return The decoded bitmap, or null if the image data could not be
* decoded.
* @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig}
* is {@link android.graphics.Bitmap.Config#HARDWARE}
* and {@link BitmapFactory.Options#inMutable} is set, if the specified color space
* is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
* function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
*/
public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) { ... }
由于這部分比較簡(jiǎn)單,下面直接給出相關(guān)示例代碼:
// 解析得到原圖的長(zhǎng)寬值,方便后面進(jìn)行局部顯示時(shí)指定需要顯示的區(qū)域。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
int width = options.outWidth;
int height = options.outHeight;
try {
// 創(chuàng)建局部解析器
InputStream inputStream = getResources().openRawResource(R.drawable.test);
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(inputStream,false);
// 指定需要顯示的矩形區(qū)域,這里要顯示的原圖的左上 1/4 區(qū)域。
Rect rect = new Rect(0, 0, width / 2, height / 2);
// 創(chuàng)建位圖配置,這里使用 RGB_565,每個(gè)像素占 2 字節(jié)。
BitmapFactory.Options regionOptions = new BitmapFactory.Options();
regionOptions.inPreferredConfig = Bitmap.Config.RGB_565;
// 創(chuàng)建得到指定區(qū)域的 Bitmap 對(duì)象并進(jìn)行顯示。
Bitmap regionBitmap = decoder.decodeRegion(rect,regionOptions);
ImageView imageView = (ImageView) findViewById(R.id.main_image);
imageView.setImageBitmap(regionBitmap);
} catch (Exception e) {
e.printStackTrace();
}
從測(cè)試結(jié)果看,確實(shí)只顯示了原圖的左上1/4區(qū)域的圖片內(nèi)容,這里不再貼出結(jié)果。
3. 加載多圖片
有時(shí)需要在應(yīng)用中同時(shí)顯示多張圖片,例如使用ListView,GridView和ViewPager時(shí),可能會(huì)需要在每一項(xiàng)都顯示一個(gè)圖片,這時(shí)情況就會(huì)變得復(fù)雜些,因?yàn)榭梢酝ㄟ^(guò)滑動(dòng)改變控件的可見(jiàn)項(xiàng),如果每增加一個(gè)可見(jiàn)項(xiàng)就加載一個(gè)圖片,同時(shí)不可見(jiàn)項(xiàng)的圖片繼續(xù)在內(nèi)存中,隨著不斷的增加,就會(huì)導(dǎo)致內(nèi)存溢出。
為了避免這種情況的內(nèi)存溢出問(wèn)題,就需要對(duì)不可見(jiàn)項(xiàng)對(duì)應(yīng)的圖片資源進(jìn)行回收,即當(dāng)前項(xiàng)被滑出屏幕的顯示區(qū)域時(shí)考慮回收相關(guān)的圖片,這時(shí)回收策略對(duì)整個(gè)應(yīng)用的性能有較大影響。
- 立即回收:在當(dāng)前項(xiàng)被滑出屏幕時(shí)立即回收?qǐng)D片資源,但如果被滑出的項(xiàng)很快又被滑入屏幕,就需要重新加載圖片,這無(wú)疑會(huì)導(dǎo)致性能的下降。
- 延遲回收:在當(dāng)前項(xiàng)被滑出屏幕時(shí)不立即回收,而是根據(jù)一定的延遲策略進(jìn)行回收,這時(shí)對(duì)延遲策略有較高要求,如果延遲時(shí)間太短就退回到立即回收狀況,如果延遲時(shí)間較長(zhǎng)就可能導(dǎo)致一段時(shí)間內(nèi),內(nèi)存中存在大量的圖片,進(jìn)而引發(fā)內(nèi)存溢出。通過(guò)上面的分析,針對(duì)加載多圖的情況,必須要采取延遲回收,而Android提供了一中基于LRU,即最近最少使用策略的內(nèi)存緩存技術(shù): LruCache, 其基本思想是,以強(qiáng)引用的方式保存外界對(duì)象,當(dāng)緩存空間達(dá)到一定限制后,再把最近最少使用的對(duì)象釋放回收,保證使用的緩存空間始終在一個(gè)合理范圍內(nèi)。
其聲明如下:
/**
* A cache that holds strong references to a limited number of values. Each time
* a value is accessed, it is moved to the head of a queue. When a value is
* added to a full cache, the value at the end of that queue is evicted and may
* become eligible for garbage collection.
*/
public class LruCache<K, V> { ... }
從聲明中,可以了解到其實(shí)現(xiàn)LRU的方式:內(nèi)部維護(hù)一個(gè)有序隊(duì)列,每當(dāng)其中的一個(gè)對(duì)象被訪問(wèn)就被移動(dòng)到隊(duì)首,這樣就保證了隊(duì)列中的對(duì)象是根據(jù)最近的使用時(shí)間從近到遠(yuǎn)排列的,即隊(duì)首的對(duì)象是最近使用的,隊(duì)尾的對(duì)象是最久之前使用的。正是基于這個(gè)規(guī)則,如果緩存達(dá)到限制后,直接把隊(duì)尾對(duì)象釋放即可。
在實(shí)際使用中,為了創(chuàng)建LruCache對(duì)象,首先要確定該緩存能夠使用的內(nèi)存大小,這是效率的決定性因素。如果緩存內(nèi)存太小,無(wú)法真正發(fā)揮緩存的效果,仍然需要頻繁的加載和回收資源;如果緩存內(nèi)存太大,可能導(dǎo)致內(nèi)存溢出的發(fā)生。在確定緩存大小的時(shí)候,要結(jié)合以下幾個(gè)因素:
- 進(jìn)程可以使用的內(nèi)存情況
- 資源的大小和需要一次在界面上顯示的資源數(shù)量
- 資源的訪問(wèn)頻率
下面給出一個(gè)簡(jiǎn)單的示例:
// 獲得進(jìn)程可以使用的最大內(nèi)存量
int maxMemory = (int) Runtime.getRuntime().maxMemory();
mCache = new LruCache<String, Bitmap>(maxMemory / 4) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
在示例中簡(jiǎn)單地把緩存大小設(shè)定為進(jìn)程可以使用的內(nèi)存的 1/4,當(dāng)然在實(shí)際項(xiàng)目中,要考慮的因素會(huì)更多。需要注意的是,在創(chuàng)建LruCache對(duì)象的時(shí)候需要重寫sizeOf方法,它用來(lái)返回每個(gè)對(duì)象的大小,是用來(lái)決定當(dāng)前緩存實(shí)際大小并判斷是否達(dá)到了內(nèi)存限制。
在創(chuàng)建了LruCache對(duì)象后,如果需要使用資源,首先到緩存中去取,如果成功取到就直接使用,否則加載資源并放入緩存中,以方便下次使用。為了加載資源的行為不會(huì)影響應(yīng)用性能,需要在子線程中去進(jìn)行,可以利用AsyncTask來(lái)實(shí)現(xiàn)。
下面是示例代碼:
public Bitmap get(String key) {
Bitmap bitmap = mCache.get(key);
if (bitmap != null) {
return bitmap;
} else {
new BitmapAsyncTask().execute(key);
return null;
}
}
private class BitmapAsyncTask extends AsyncTask<String, Void, Bitmap> {
@Override
protected Bitmap doInBackground(String... url) {
Bitmap bitmap = getBitmapFromUrl(url[0]);
if (bitmap != null) {
mCache.put(url[0],bitmap);
}
return bitmap;
}
private Bitmap getBitmapFromUrl(String url) {
Bitmap bitmap = null;
// 在這里要利用給定的 url 信息從網(wǎng)絡(luò)獲取 bitmap 信息.
return bitmap;
}
}
示例中,在無(wú)法從緩存中獲取資源的時(shí)候,會(huì)根據(jù)url信息加載網(wǎng)絡(luò)資源,當(dāng)前并沒(méi)有給出完整的代碼,有興趣的同學(xué)可以自己去完善。
4. 總結(jié)
本文主要針對(duì)不同的圖片加載場(chǎng)景提出了不同的加載策略,以保證在加載和顯示過(guò)程中既然能滿足基本的顯示需求,又不會(huì)導(dǎo)致內(nèi)存溢出,具體包括針對(duì)單個(gè)圖片的壓縮顯示,局部顯示和針對(duì)多圖的內(nèi)存緩存技術(shù),如若有表述不清甚至錯(cuò)誤的地方,請(qǐng)及時(shí)提出,大家一起學(xué)習(xí)。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- Android如何使用Glide加載清晰長(zhǎng)圖
- android實(shí)現(xiàn)長(zhǎng)圖加載效果
- Android仿微博加載長(zhǎng)圖滾動(dòng)查看效果
- Android 官推 kotlin-first 的圖片加載庫(kù)——Coil的使用入門
- Android如何加載Base64編碼格式圖片
- Android Fresco圖片加載優(yōu)化的方案
- Android實(shí)現(xiàn)圖片加載進(jìn)度提示
- Android適配利用webview加載后圖片顯示過(guò)大的問(wèn)題解決
- Android框架Volley之利用Imageloader和NetWorkImageView加載圖片的方法
- Android框架Volley使用:ImageRequest請(qǐng)求實(shí)現(xiàn)圖片加載
- Android加載長(zhǎng)圖的多種方案分享
相關(guān)文章
安卓(Android)聊天機(jī)器人實(shí)現(xiàn)代碼分享
這是一個(gè)安卓智能聊天機(jī)器人的源碼,采用了仿微信的風(fēng)格設(shè)計(jì),調(diào)用的是圖靈機(jī)器人的API,能夠?qū)崿F(xiàn)智能聊天、講故事、講笑話、查天氣、查公交等豐富的功能2015-11-11
Android開(kāi)發(fā)實(shí)現(xiàn)ListView和adapter配合顯示圖片和文字列表功能示例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)ListView和adapter配合顯示圖片和文字列表功能,涉及Android使用ListView結(jié)合adapter適配器實(shí)現(xiàn)圖文顯示功能相關(guān)的布局、解析、權(quán)限控制等操作技巧,需要的朋友可以參考下2019-04-04
Android UI控件之ImageSwitcher實(shí)現(xiàn)圖片切換效果
這篇文章主要為大家詳細(xì)介紹了Android UI控件之ImageSwitcher實(shí)現(xiàn)圖片切換效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
android仿微信表情雨下落效果的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于android仿微信表情雨下落效果的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-09-09
去掉RecycleView或者ListView上下滑動(dòng)陰影的方法
下面小編就為大家分享一篇去掉RecycleView或者ListView上下滑動(dòng)陰影的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
Android開(kāi)發(fā) OpenGL ES繪制3D 圖形實(shí)例詳解
這篇文章主要介紹了Android開(kāi)發(fā) OpenGL ES繪制3D 圖形實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2016-09-09
RxJava實(shí)戰(zhàn)之訂閱流基本原理示例解析
這篇文章主要為大家介紹了RxJava實(shí)戰(zhàn)之訂閱流基本原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Android使用Canvas?2D實(shí)現(xiàn)循環(huán)菜單效果
循環(huán)菜單有很多種自定義方式,我們可以利用ViewPager或者RecyclerView?+?CarouselLayoutManager?或者RecyclerView?+?PageSnapHelper來(lái)實(shí)現(xiàn)這種效果,今天我們使用Canvas?2D來(lái)實(shí)現(xiàn)這種效果,感興趣的朋友可以參考下2024-01-01
Android?Flutter在點(diǎn)擊事件上添加動(dòng)畫效果實(shí)現(xiàn)全過(guò)程
這篇文章主要給大家介紹了關(guān)于Android?Flutter在點(diǎn)擊事件上添加動(dòng)畫效果實(shí)現(xiàn)的相關(guān)資料,通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)Android具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-03-03
Android Scroll實(shí)現(xiàn)彈性滑動(dòng)_列表下拉彈性滑動(dòng)的示例代碼
下面小編就為大家分享一篇Android Scroll實(shí)現(xiàn)彈性滑動(dòng)_列表下拉彈性滑動(dòng)的示例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01

