久久精品国产精品国产精品污,男人扒开添女人下部免费视频,一级国产69式性姿势免费视频,夜鲁夜鲁很鲁在线视频 视频,欧美丰满少妇一区二区三区,国产偷国产偷亚洲高清人乐享,中文 在线 日韩 亚洲 欧美,熟妇人妻无乱码中文字幕真矢织江,一区二区三区人妻制服国产

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

pAdTy_1 构建图形和动画应用程序

發(fā)布時(shí)間:2024/3/13 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 pAdTy_1 构建图形和动画应用程序 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

2015.11.12-11.18
個(gè)人英文閱讀練習(xí)筆記。原文地址:http://developer.android.com/training/building-graphics.html。

2015.11.12
此部分內(nèi)容將展示如何用圖形來完成任務(wù)以給應(yīng)用程序帶來競爭優(yōu)勢。如果您想超越基本的用戶界面而想創(chuàng)造美麗的視覺體驗(yàn),此部分內(nèi)容將會幫助您完成此心愿。

1. 有效的顯示位圖

在保持用戶界面的響應(yīng)性時(shí),如何加載和處理位圖并避免超過內(nèi)存限制。

學(xué)習(xí)保持用戶界面組件的響應(yīng)性并避免超過應(yīng)用程序的內(nèi)存限制的方法來處理和加載位圖對象。如果不那么仔細(xì),位圖能夠快速消耗掉可用的內(nèi)存預(yù)算隨之導(dǎo)致可怕的異常(java.lang.OutofMemoryError: bitmap size exceeds VM budget)而讓應(yīng)用程序崩潰。

以下是在安卓應(yīng)用程序中載入位圖時(shí)需要機(jī)警的幾個(gè)原因:
- 移動(dòng)設(shè)備的系統(tǒng)資源通常都比較受限制。安卓設(shè)備只能給每個(gè)應(yīng)用程序16MB的可用內(nèi)存空間。安卓兼容性定義文檔(Android Compatibility Definition Document)第3.7節(jié)。虛擬機(jī)兼容會給各種不同尺寸和密度的屏幕下的應(yīng)用程序最小的內(nèi)存空間。應(yīng)用程序應(yīng)被優(yōu)化到能夠在最小內(nèi)存空間運(yùn)行的程度。然而,許多設(shè)備都會配置更高的內(nèi)存限制。
- 位圖會占用大量的內(nèi)存,尤其是像照片這樣的富圖。例如,Galaxy Mexus設(shè)備上的相機(jī)拍照達(dá)2592x1936像素(500萬像素)。如果位圖配置使用ARGB_8888(安卓2.3版本以前默認(rèn)),載入此照片消耗19MB內(nèi)存(2592x1936x4字節(jié)),一下子就將某些設(shè)備上給應(yīng)用程序預(yù)分配的可用空間給消耗了。
- 安卓應(yīng)用程序用戶界面在同一時(shí)刻需要載入幾張位圖。像ListView、GridView以及ViewPager這樣的組件通常在同時(shí)包含多張位圖(有的是跟隨用戶操作而即將展現(xiàn)的圖片)。

1.1 有效地載入大型位圖

在不超過每個(gè)應(yīng)用程序內(nèi)存限制的情況下解碼大型位圖。

不同的圖片不同的形狀和尺寸。在許多情況下應(yīng)用程序的用戶界面所需的圖片都比實(shí)際的圖片要小。例如,系統(tǒng)的畫廊應(yīng)用程序展示的用安卓設(shè)備相機(jī)拍的圖片的分辨率通常就比設(shè)備屏幕的密度要高。

鑒于有限的內(nèi)存,理想情況下只需加載一個(gè)低分辨率的版本到內(nèi)存中。低版本分辨率應(yīng)該要跟顯示它的用戶界面組件的尺寸匹配。一個(gè)擁有高分辨率的圖片不會給顯示帶來好處,反而會更多的占用珍貴的內(nèi)存并會引起額外的性能開銷。

此節(jié)將通過載入圖片的一小部分的方式解碼圖片以不超過應(yīng)用程序有限的內(nèi)存。

(1) 讀取位圖的尺寸和類型
BitmapFactory類提供了幾種解碼方法(decodeByteArray(),decodeFile(),decodeResource()等)來根據(jù)各種類型資源創(chuàng)建Bitmap。給予圖片數(shù)據(jù)資源選擇最合適的解碼方法。這些方法嘗試為所構(gòu)建的位圖分配內(nèi)存,因此就能夠很容易檢測出outOfMemory異常。每種類型的解碼方法都有額外的可以通過BitmapFactory.Options類制定編碼選項(xiàng)的簽名。解碼時(shí)將inJustDecodeBounds特性設(shè)置為ture以避免內(nèi)存分配,通過設(shè)置位圖的outWidth、outHeight和outMimeType可返回null。此項(xiàng)技術(shù)允許在構(gòu)建(以及內(nèi)存分配)位圖之前獲取到圖片的尺寸和類型。

BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;

欲避免java.lang.outOfMemory異常,在解碼位圖時(shí)檢查位圖的尺寸,除非確定圖片不會引來此異常。

(2) 載入圖片的縮小版本到內(nèi)存
在知道圖片的尺寸后,此數(shù)據(jù)就可以用來判斷是要將整張圖片都載入內(nèi)存還是將代替此圖片的子樣例載入內(nèi)存。以下是需要考慮的因素:
- 估算整張圖片會占用的內(nèi)存。
- 被用來載入圖片的內(nèi)存是否會被應(yīng)用程序的其它部分使用。
- 圖片將要顯示的目標(biāo)ImageView或用戶界面組件的尺寸。
- 現(xiàn)有設(shè)備屏幕尺寸和密度。

舉例,如果一張1024x768像素的圖片最終會被略縮顯示在128x96像素的ImageView中,那么此圖片就不值得全被載入到內(nèi)存中。

欲告知解碼器解碼圖片的子樣本,載入一個(gè)更低像素版本的圖片到內(nèi)存中,需要將BitmapFactory.Options中的inSampleSize設(shè)置為ture。例如,一張像素為2048x1536的圖片用inSampleSize值為4來解碼會產(chǎn)生約512x384的位圖。將解碼后的圖片載入內(nèi)存只需花0.75MB,而將整張圖片載入內(nèi)存會消耗12MB內(nèi)存(假設(shè)位圖配置為ARGB_8888)。基于目標(biāo)寬度和高度,有一種將樣本尺寸計(jì)算出2的指數(shù)的方法。

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {// Raw height and width of imagefinal int height = options.outHeight;final int width = options.outWidth;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {final int halfHeight = height / 2;final int halfWidth = width / 2;// Calculate the largest inSampleSize value that is a power of 2 and keeps both// height and width larger than the requested height and width.while ((halfHeight / inSampleSize) > reqHeight&& (halfWidth / inSampleSize) > reqWidth) {inSampleSize *= 2;}}return inSampleSize; }

注:解碼器最終將值舍到最接近2的指數(shù)的值。

欲用這種方法,首先要用被設(shè)置為true的inJusDecodeBounds解碼一次,將選項(xiàng)傳遞再用值為false的inSampleSize和inJustDecodeBounds再解碼一次。

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {// First decode with inJustDecodeBounds=true to check dimensionsfinal BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(res, resId, options);// Calculate inSampleSizeoptions.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);// Decode bitmap with inSampleSize setoptions.inJustDecodeBounds = false;return BitmapFactory.decodeResource(res, resId, options); }

此方法讓載入任意大小尺寸位圖到ImageView變得簡單。如在ImageView中顯示一個(gè)100x100像素的縮略圖時(shí),用以下代碼即可實(shí)現(xiàn):

mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

可以用類似的步驟解碼其它的資源來形成位圖,通過替代合適的BitmapFactory.decode*方法即可。

1.2 在用戶界面線程之外的線程處理位圖

位圖處理(重新設(shè)置尺寸,從遠(yuǎn)端下載等)不再主用戶界面所在線程中處理。此部分筆記將帶您學(xué)習(xí)用AsynTask創(chuàng)建后臺線程來處理位圖并解釋如何處理并發(fā)問題。

在“有效地載入大型位圖”一節(jié)中所討論的BitmapFactory.decode*方法,如果圖片資源數(shù)據(jù)在硬盤或網(wǎng)絡(luò)( 或其它任何不在內(nèi)存的位置)上,都不應(yīng)該在用戶界面主線程中使用這些方法。載入圖片所花的時(shí)間是不可預(yù)測的,它基于各種各樣的因素(從硬盤或網(wǎng)絡(luò)讀取數(shù)據(jù)的速度,圖片尺寸,CPU的性能等)。如果因載入圖片阻礙了用戶界面線程,系統(tǒng)所運(yùn)行的應(yīng)用程序?qū)⒉痪哂袑?shí)時(shí)的響應(yīng)性,用戶也極有可能選擇將此應(yīng)用程序關(guān)閉(見設(shè)計(jì)具響應(yīng)性的應(yīng)用程序獲取更多信息)。

(1) 使用異步任務(wù)(AsyncTask)
AsyncTask類提供了一種簡單的方式在后臺線程中執(zhí)行一些任務(wù)并將結(jié)果返回到用戶主線程中。欲使用此類,需要?jiǎng)?chuàng)建一個(gè)子類并重寫所提供的方法。以下是使用AsyncTask和decodeSampledBitmapFromResource將一張大型圖片載入到ImageView中的示例:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {private final WeakReference<ImageView> imageViewReference;private int data = 0;public BitmapWorkerTask(ImageView imageView) {// Use a WeakReference to ensure the ImageView can be garbage collectedimageViewReference = new WeakReference<ImageView>(imageView);}// Decode image in background.@Overrideprotected Bitmap doInBackground(Integer... params) {data = params[0];return decodeSampledBitmapFromResource(getResources(), data, 100, 100));}// Once complete, see if ImageView is still around and set bitmap.@Overrideprotected void onPostExecute(Bitmap bitmap) {if (imageViewReference != null && bitmap != null) {final ImageView imageView = imageViewReference.get();if (imageView != null) {imageView.setImageBitmap(bitmap);}}} }

為ImageView添加的WeakReference保證了AsyncTask不會阻止ImageView以及其引用的任何東西收集垃圾信息。不敢保證當(dāng)任務(wù)執(zhí)行完后ImageView仍舊還在,所以必須在onPostExecute()中檢查其引用。就此例來說,在任務(wù)結(jié)束之前用戶導(dǎo)航離開活動(dòng)或者配置發(fā)生改變時(shí),ImageView可能不再存在。

欲異步開始載入位圖,簡單的創(chuàng)造一個(gè)新的任務(wù)并執(zhí)行與載入相關(guān)的代碼即可:

public void loadBitmap(int resId, ImageView imageView) {BitmapWorkerTask task = new BitmapWorkerTask(imageView);task.execute(resId); }

2015.11.13
(2) 處理并發(fā)
ListView、GridView等這些常見組件和AsyncTask結(jié)合使用會引來引來另外一個(gè)問題。為了有效地利用內(nèi)存,隨著用戶滑動(dòng)滾動(dòng)條,這些組件會被重復(fù)利用為子視圖顯示。如果每個(gè)子視圖都觸發(fā)一個(gè)AsyncTask,不敢保證當(dāng)AsyncTask完成時(shí),對應(yīng)的視圖還未被重復(fù)利用來顯示另外一個(gè)子視圖。另外,也不能保證各異步線程是在其它線程利用完視圖后再開始利用此視圖。

博客“高性能的多線程(Multithreading for Performance)”深入的討論了處理并發(fā)問題,并提供了當(dāng)某任務(wù)完成后何AsyncTask將獲得ImageView的引用何AsyncTask稍后再引用ImageView的解決方法。使用相似的方法,前一節(jié)提到的AsyncTask能夠被擴(kuò)展為一個(gè)成熟的模式。

創(chuàng)建一個(gè)微型的Drawable子類來存儲一個(gè)返回到工作任務(wù)的引用。在這種情況下,當(dāng)任務(wù)執(zhí)行完時(shí),BitmapDrawable將圖片展示在ImageView中:

static class AsyncDrawable extends BitmapDrawable {private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;public AsyncDrawable(Resources res, Bitmap bitmap,BitmapWorkerTask bitmapWorkerTask) {super(res, bitmap);bitmapWorkerTaskReference =new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);}public BitmapWorkerTask getBitmapWorkerTask() {return bitmapWorkerTaskReference;

在執(zhí)行bitmapWorkerTask以前,可以創(chuàng)建AsyncDrawable并將其綁定到目標(biāo)ImageView上:

public void loadBitmap(int resId, ImageView imageView) {if (cancelPotentialWork(resId, imageView)) {final BitmapWorkerTask task = new BitmapWorkerTask(imageView);final AsyncDrawable asyncDrawable =new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);imageView.setImageDrawable(asyncDrawable);task.execute(resId);} }

在以上代碼樣例中涉及到的cancelPotentialWork方法是用來檢查是否有另外一個(gè)正在運(yùn)行的任務(wù)已經(jīng)在使用ImageView。如果有,此方法將調(diào)用cancel()方法來取消之前的任務(wù)。在少數(shù)情況下,新任務(wù)數(shù)據(jù)匹配已經(jīng)存在任務(wù)并且不需要其它的具體步驟。以下是cancelPotentialWork方法的一種實(shí)現(xiàn):

public static boolean cancelPotentialWork(int data, ImageView imageView) {final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);if (bitmapWorkerTask != null) {final int bitmapData = bitmapWorkerTask.data;// If bitmapData is not yet set or it differs from the new dataif (bitmapData == 0 || bitmapData != data) {// Cancel previous taskbitmapWorkerTask.cancel(true);} else {// The same work is already in progressreturn false;}}// No task associated with the ImageView, or an existing task was cancelledreturn true; }

以上代碼所使用的getBitmapWorkerTask()方法用來檢索所任務(wù)涉及的ImageView:

private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {if (imageView != null) {final Drawable drawable = imageView.getDrawable();if (drawable instanceof AsyncDrawable) {final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;return asyncDrawable.getBitmapWorkerTask();}}return null; }

最后一步是更新BitmapWorkerTask中的onPostExecte()以檢查任務(wù)是否被取消,斌檢查當(dāng)前任務(wù)是否關(guān)聯(lián)上了ImageView:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {...@Overrideprotected void onPostExecute(Bitmap bitmap) {if (isCancelled()) {bitmap = null;}if (imageViewReference != null && bitmap != null) {final ImageView imageView = imageViewReference.get();final BitmapWorkerTask bitmapWorkerTask =getBitmapWorkerTask(imageView);if (this == bitmapWorkerTask && imageView != null) {imageView.setImageBitmap(bitmap);}}} }

這樣的實(shí)現(xiàn)就適合用ListView、GridView以及其它的組件被重復(fù)用作子視圖顯示了。在將圖片設(shè)置到ImageView的地方簡單的調(diào)用loadBitmap。例如,在GridView的實(shí)現(xiàn)中,是調(diào)用getView()方法來實(shí)現(xiàn)的,此在后一節(jié)中描述。

1.3 緩存位圖

此節(jié)教您在載入多張位圖時(shí)如何使用內(nèi)存和硬盤位圖緩存來提升主用戶界面的響應(yīng)性和流動(dòng)性。

載入一張位圖到用戶界面是比較簡單的,然而當(dāng)需要在同一時(shí)間就載入大量位圖時(shí)就會變得復(fù)雜許多。在許多情況(如ListView、GridView或ViewPager組件)下,可能很快滾動(dòng)到屏幕上顯示的數(shù)量是無限的。

當(dāng)向下移動(dòng)屏幕時(shí)通過重復(fù)利用組件來表示子視圖的方式來保持內(nèi)存消耗量不上升。假如不保持長期的引用位圖,垃圾回收器會釋放載入的位圖。這一點(diǎn)固然是好,但為了保持流暢和快速的加載用戶界面,當(dāng)圖片每次重新回到屏幕上時(shí)也想避免次次都去處理它。一段內(nèi)存或硬盤緩存 能夠滿足組件快速重載入之前經(jīng)處理過的圖片。

此節(jié)將展示當(dāng)載入多張位圖時(shí),使用內(nèi)存或硬盤位圖緩存來提升用戶界面的流動(dòng)性和響應(yīng)性。

(1) 使用內(nèi)存緩存
占用應(yīng)用程序可用內(nèi)存空間的內(nèi)存緩存用來保存位圖可被快速訪問。LruCache類(此類也存在于API level 4 對應(yīng)的支持庫中)特別適合于位圖緩存、在強(qiáng)引用LinkedHashMap中保持最近的引用對象、在緩存越界之前驅(qū)逐最近引用最少的對象的任務(wù)。

注:在以前,流行的內(nèi)存緩存的實(shí)現(xiàn)是SoftReference或WeakReference位圖緩存,但現(xiàn)在不推薦此種緩存。從Android 2.3(API level 9)開始,垃圾回收器變得更加強(qiáng)大,它回收讓對象幾乎無效的軟/弱引用。另外,在Android 3.0(API level 11)之前,位圖的回收數(shù)據(jù)沒有被提前釋放而是被保存在本地內(nèi)存中,這可能會引起應(yīng)用程序超越其內(nèi)存限制而崩潰。

欲給LruCache選擇一個(gè)合適的尺寸,許多因素都應(yīng)該被納入考慮,如:
- 活動(dòng)跟應(yīng)用程序使用后所剩下的內(nèi)存大小。
- 多少圖片會被同一時(shí)間載入到屏幕上?需要準(zhǔn)備多少圖片到屏幕上?
- 設(shè)備的屏幕尺寸和密度是多少?對于相同數(shù)量的圖片,像Galaxy Nexus這樣屏幕密度格外高(xhdpi)的設(shè)備比Nexus S(hdpi)設(shè)備所要分配的內(nèi)存緩存要大。
- 根據(jù)位圖的尺寸和配置計(jì)算到圖片所會占用的內(nèi)存有多大?
- 圖片被訪問的頻率有多大?是否其中有一部分圖片的訪問頻率會高于其它圖片?如果是這樣,可能需要總是要在內(nèi)存中保存特定的內(nèi)容,設(shè)置為不同組的位圖分配對應(yīng)的LruCache對象。
- 需要平衡質(zhì)量和質(zhì)量么?有時(shí)選擇存儲大數(shù)量低質(zhì)量的位圖可能會更有用,而在后臺進(jìn)程中載入高質(zhì)量的圖片。

沒有適合所有應(yīng)用程序的特定的尺寸和規(guī)則,需要根據(jù)具體情況分析用量并作出相應(yīng)的決策。如果緩存太小會引起附加開銷,如果緩存太大就有可能會引起java.lang.OutOfMemory異常并會讓應(yīng)用程序智能使用很小的內(nèi)存。

以下是為位圖設(shè)置LruCache的示例代碼:

private LruCache<String, Bitmap> mMemoryCache;@Override protected void onCreate(Bundle savedInstanceState) {...// Get max available VM memory, exceeding this amount will throw an// OutOfMemory exception. Stored in kilobytes as LruCache takes an// int in its constructor.final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);// Use 1/8th of the available memory for this memory cache.final int cacheSize = maxMemory / 8;mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap bitmap) {// The cache size will be measured in kilobytes rather than// number of items.return bitmap.getByteCount() / 1024;}};... }public void addBitmapToMemoryCache(String key, Bitmap bitmap) {if (getBitmapFromMemCache(key) == null) {mMemoryCache.put(key, bitmap);} }public Bitmap getBitmapFromMemCache(String key) {return mMemoryCache.get(key); }

注:在此例中,將應(yīng)用程序內(nèi)存的八分之一分配作為了緩存。對于通常(hdpi)設(shè)備來說,這是緩存的最小值,約為4MB(32/8)。在一個(gè)800x480分辨的設(shè)備上,一個(gè)全屏的GridView填充的圖片會占用約為1.5MB(800*480*4字節(jié)),所以此緩存約能存2.5張這樣的圖片。

當(dāng)載入位圖到ImageView中時(shí),LruCache最先被檢查。如果尋到入口,它會立馬被用來更新ImageView,否則會催生一個(gè)后臺線程來處理圖片:

public void loadBitmap(int resId, ImageView imageView) {final String imageKey = String.valueOf(resId);final Bitmap bitmap = getBitmapFromMemCache(imageKey);if (bitmap != null) {mImageView.setImageBitmap(bitmap);} else {mImageView.setImageResource(R.drawable.image_placeholder);BitmapWorkerTask task = new BitmapWorkerTask(mImageView);task.execute(resId);} }

BitmapWorkerTask也需要被更新以添加到內(nèi)存緩存的入口:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {...// Decode image in background.@Overrideprotected Bitmap doInBackground(Integer... params) {final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);return bitmap;}... }

(2) 使用硬盤緩存
內(nèi)存緩沖區(qū)對最近常被查看的視圖的訪問速度的提升很有用,然而不能夠讓圖片依賴此種緩存。像GridView這種擁有大量數(shù)據(jù)集的組件很容易就填滿內(nèi)存緩沖區(qū)。應(yīng)用程序還可能會被諸如來電這樣的任務(wù)打斷,如此,在后臺的線程就可能會被殺死即內(nèi)存緩沖區(qū)會被銷毀。一旦用戶恢復(fù)應(yīng)用程序后,應(yīng)用程序不得不再次重新處理每張圖片。

在以上描述的情況中可以使用硬盤緩存來保留經(jīng)處理的位圖并當(dāng)內(nèi)存緩沖區(qū)中的圖片不可用時(shí)能減少載入次數(shù)。當(dāng)然,從硬盤中取圖片會比從內(nèi)存載入慢且因?yàn)樽x硬盤次數(shù)不可預(yù)測,所以此舉需要在后臺線程中完成。

注:像畫廊應(yīng)用程序中訪問頻率較高的圖片使用ContentProvider來提供圖片緩存更合適。

Android 源碼中的類樣碼使用DiskLruCache實(shí)現(xiàn)。以下代碼在已有內(nèi)存緩沖區(qū)后增加硬盤緩沖區(qū):

private DiskLruCache mDiskLruCache; private final Object mDiskCacheLock = new Object(); private boolean mDiskCacheStarting = true; private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB private static final String DISK_CACHE_SUBDIR = "thumbnails";@Override protected void onCreate(Bundle savedInstanceState) {...// Initialize memory cache...// Initialize disk cache on background threadFile cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);new InitDiskCacheTask().execute(cacheDir);... }class InitDiskCacheTask extends AsyncTask<File, Void, Void> {@Overrideprotected Void doInBackground(File... params) {synchronized (mDiskCacheLock) {File cacheDir = params[0];mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);mDiskCacheStarting = false; // Finished initializationmDiskCacheLock.notifyAll(); // Wake any waiting threads}return null;} }class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {...// Decode image in background.@Overrideprotected Bitmap doInBackground(Integer... params) {final String imageKey = String.valueOf(params[0]);// Check disk cache in background threadBitmap bitmap = getBitmapFromDiskCache(imageKey);if (bitmap == null) { // Not found in disk cache// Process as normalfinal Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));}// Add final bitmap to cachesaddBitmapToCache(imageKey, bitmap);return bitmap;}... }public void addBitmapToCache(String key, Bitmap bitmap) {// Add to memory cache as beforeif (getBitmapFromMemCache(key) == null) {mMemoryCache.put(key, bitmap);}// Also add to disk cachesynchronized (mDiskCacheLock) {if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {mDiskLruCache.put(key, bitmap);}} }public Bitmap getBitmapFromDiskCache(String key) {synchronized (mDiskCacheLock) {// Wait while disk cache is started from background threadwhile (mDiskCacheStarting) {try {mDiskCacheLock.wait();} catch (InterruptedException e) {}}if (mDiskLruCache != null) {return mDiskLruCache.get(key);}}return null; }// Creates a unique subdirectory of the designated app cache directory. Tries to use external // but if not mounted, falls back on internal storage. public static File getDiskCacheDir(Context context, String uniqueName) {// Check if media is mounted or storage is built-in, if so, try and use external cache dir// otherwise use internal cache dirfinal String cachePath =Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :context.getCacheDir().getPath();return new File(cachePath + File.separator + uniqueName); }

注:即使是初始化硬盤緩沖區(qū)也需要硬盤操作且不應(yīng)在主線程中完成這個(gè)過程。然而,在初始化之前確實(shí)也有訪問緩存的機(jī)會。為了解決這個(gè)問題,在以上代碼的實(shí)現(xiàn)中,使用鎖住一個(gè)對象來確保在初始化緩沖區(qū)之前不能讀硬盤緩沖區(qū)。

當(dāng)內(nèi)存緩沖區(qū)在用戶界面線程被完成后,當(dāng)硬盤緩沖區(qū)在后臺線程中被創(chuàng)建后。與硬盤相關(guān)的操作不要在用戶界面線程中操作。當(dāng)圖片處理完成后,最終的位圖被同時(shí)增加到內(nèi)存和硬盤緩沖區(qū)中,供后續(xù)使用。

(3) 處理配置更改
諸如屏幕方向改變這樣的運(yùn)行時(shí)配置改變時(shí),會引起安卓銷毀并用新配置重啟運(yùn)行的活動(dòng)(關(guān)于此行為的更多信息見Handling Runtime Changes)。為讓用戶在配置改變時(shí)還能夠感受到流利快速的用戶體驗(yàn)需要避免再次處理所有的圖片。

幸運(yùn)的是,在Use a Memory Cache節(jié)為位圖創(chuàng)建了好用的內(nèi)存緩沖區(qū)。使用通過調(diào)用setRetainInstance(true)保存的碎片能夠?qū)⒕彌_區(qū)傳遞給新的活動(dòng)實(shí)例。在活動(dòng)被重建后,它能夠重新獲得附加的碎片且能夠獲取對存在緩沖區(qū)對象的訪問權(quán),允許快速的提取并重新填充到ImageView對象中。

以下代碼處理配置改變后用碎片重新獲取LruCache的過程:

private LruCache<String, Bitmap> mMemoryCache;@Override protected void onCreate(Bundle savedInstanceState) {...RetainFragment retainFragment =RetainFragment.findOrCreateRetainFragment(getFragmentManager());mMemoryCache = retainFragment.mRetainedCache;if (mMemoryCache == null) {mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {... // Initialize cache here as usual}retainFragment.mRetainedCache = mMemoryCache;}... }class RetainFragment extends Fragment {private static final String TAG = "RetainFragment";public LruCache<String, Bitmap> mRetainedCache;public RetainFragment() {}public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);if (fragment == null) {fragment = new RetainFragment();fm.beginTransaction().add(fragment, TAG).commit();}return fragment;}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setRetainInstance(true);} }

旋轉(zhuǎn)手機(jī)用重新獲得/沒獲得碎片的情況來測試此段代碼。您應(yīng)該注意無滯后情況下,幾乎是立即從重獲的緩沖區(qū)中裝載圖片到活動(dòng)中去的。如果內(nèi)存緩沖區(qū)中無相應(yīng)的圖片就到硬盤緩沖區(qū)找尋找,如果硬盤緩沖區(qū)中亦無,那么就像平常一樣處理。

1.4 管理位圖內(nèi)存

此節(jié)解釋如何管理位圖內(nèi)存來最大化的提升應(yīng)用程序性能。

除了在緩存位圖中描述措施外,還有另外一些特殊的方法可以用來優(yōu)化垃圾回收器和位圖的重使用。具體的策略基于具體的安卓系統(tǒng)版本。BitmapFun應(yīng)用程序示例包含展示設(shè)計(jì)跨不同安卓版本的應(yīng)用程序的類。

在正式開始此節(jié)內(nèi)容之前,展示下安卓管理位圖內(nèi)存的演化過程:
- 在安卓2.2(API level 8)及更低的版本中,當(dāng)垃圾回收器工作時(shí),應(yīng)用程序中的線程將停止。這會給應(yīng)用程序引起降低性能的滯后。Android 2.3增加了并發(fā)的垃圾回收器,這意味著在位圖不再被引用后內(nèi)存將被回收來供應(yīng)用程序重新使用。
- 在安卓2.3.3(API level 10)及更低版本中,位圖的像素?cái)?shù)據(jù)(backing pixel data)被保存在本地內(nèi)存中。它跟位圖本身獨(dú)立,位圖被保存在Dalvik堆中。保存在本地內(nèi)存中的像素?cái)?shù)據(jù)不會以預(yù)測的方式釋放,這可能會導(dǎo)致超出內(nèi)存限制而使應(yīng)用程序崩潰。從安卓3.0(API level 11)開始,像素?cái)?shù)據(jù)跟相應(yīng)的位圖一起存儲在Dalvik堆中。

以下幾節(jié)將描述在不同安卓版本上如何優(yōu)化位圖內(nèi)存管理。

(1) 在安卓2.3.3及更低版本中管理內(nèi)存
在安卓2.3.3(API level 10)及更低版本中,推薦使用recycle()。如果在應(yīng)用程序中顯示大量的位圖,很有可能出現(xiàn)outOfMemoryError()錯(cuò)誤。recycle()方法能夠盡快讓應(yīng)用程序重新獲得位圖所占用的內(nèi)存。

注:只有在確定位圖不再被使用時(shí)使用recycle()。若在調(diào)用recycle()后再嘗試?yán)L制位圖,將會出現(xiàn)錯(cuò)誤:“Canvas:嘗試去用回收的位圖”。

以下代碼片段為調(diào)用recycle()的示例。此程序用引用計(jì)數(shù)(用mDisplayRefCount和mCacheRefCount)來跟蹤當(dāng)前是否有位圖顯示或在緩存中。當(dāng)滿足以下條件代碼將回收位圖:
- 引用計(jì)數(shù)mDisplayRefCount和mCacheRefCount都為0.
- 位圖不為null且位圖還未被回收。

private int mCacheRefCount = 0; private int mDisplayRefCount = 0; ... // Notify the drawable that the displayed state has changed. // Keep a count to determine when the drawable is no longer displayed. public void setIsDisplayed(boolean isDisplayed) {synchronized (this) {if (isDisplayed) {mDisplayRefCount++;mHasBeenDisplayed = true;} else {mDisplayRefCount--;}}// Check to see if recycle() can be called.checkState(); }// Notify the drawable that the cache state has changed. // Keep a count to determine when the drawable is no longer being cached. public void setIsCached(boolean isCached) {synchronized (this) {if (isCached) {mCacheRefCount++;} else {mCacheRefCount--;}}// Check to see if recycle() can be called.checkState(); }private synchronized void checkState() {// If the drawable cache and display ref counts = 0, and this drawable// has been displayed, then recycle.if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed&& hasValidBitmap()) {getBitmap().recycle();} }private synchronized boolean hasValidBitmap() {Bitmap bitmap = getBitmap();return bitmap != null && !bitmap.isRecycled(); }

2015.11.14
(2) 在安卓3.0及更高版本中管理內(nèi)存
安卓3.0(API level 11)介紹了BitmapFactory.Options.inBitmap域。如果此選項(xiàng)被設(shè)置,當(dāng)載入內(nèi)容時(shí)解碼方法將會用此選項(xiàng)去重新使用存在的位圖。這就意味著位圖的內(nèi)存被重用,如此會導(dǎo)致性能的提升并省掉了內(nèi)存的分配和釋放。然而,用inBitmp也有幾個(gè)限制。尤其是在安卓4.4之前(API level 19),只支持相等尺寸的位圖。更多細(xì)節(jié)見inBitmap的文檔。

[1] 保存位圖供以后使用
以下代碼片段演示如何保存位圖來供以后使用。但應(yīng)用程序運(yùn)行在安卓3.0或更高版本中且位圖從LruCache中被驅(qū)逐了出來,對位圖的一個(gè)軟應(yīng)用被放置在HashSet中,供稍后可能用inBitmap來使用位圖:

Set<SoftReference<Bitmap>> mReusableBitmaps; private LruCache<String, BitmapDrawable> mMemoryCache;// If you're running on Honeycomb or newer, create a // synchronized HashSet of references to reusable bitmaps. if (Utils.hasHoneycomb()) {mReusableBitmaps =Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>()); }mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {// Notify the removed entry that is no longer being cached.@Overrideprotected void entryRemoved(boolean evicted, String key,BitmapDrawable oldValue, BitmapDrawable newValue) {if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {// The removed entry is a recycling drawable, so notify it// that it has been removed from the memory cache.((RecyclingBitmapDrawable) oldValue).setIsCached(false);} else {// The removed entry is a standard BitmapDrawable.if (Utils.hasHoneycomb()) {// We're running on Honeycomb or later, so add the bitmap// to a SoftReference set for possible use with inBitmap later.mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));}}} .... }

[2] 使用存在的位圖
應(yīng)用程序在運(yùn)行時(shí),解碼方法會檢查是否有存在的位圖可用。舉例如下:

public static Bitmap decodeSampledBitmapFromFile(String filename,int reqWidth, int reqHeight, ImageCache cache) {final BitmapFactory.Options options = new BitmapFactory.Options();...BitmapFactory.decodeFile(filename, options);...// If we're running on Honeycomb or newer, try to use inBitmap.if (Utils.hasHoneycomb()) {addInBitmapOptions(options, cache);}...return BitmapFactory.decodeFile(filename, options); }

addInBitmapOptions() inBitmap inBitmap

private static void addInBitmapOptions(BitmapFactory.Options options,ImageCache cache) {// inBitmap only works with mutable bitmaps, so force the decoder to// return mutable bitmaps.options.inMutable = true;if (cache != null) {// Try to find a bitmap to use for inBitmap.Bitmap inBitmap = cache.getBitmapFromReusableSet(options);if (inBitmap != null) {// If a suitable bitmap has been found, set it as the value of// inBitmap.options.inBitmap = inBitmap;}} }// This method iterates through the reusable bitmaps, looking for one // to use for inBitmap: protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {Bitmap bitmap = null;if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {synchronized (mReusableBitmaps) {final Iterator<SoftReference<Bitmap>> iterator= mReusableBitmaps.iterator();Bitmap item;while (iterator.hasNext()) {item = iterator.next().get();if (null != item && item.isMutable()) {// Check to see it the item can be used for inBitmap.if (canUseForInBitmap(item, options)) {bitmap = item;// Remove from reusable set so it can't be used again.iterator.remove();break;}} else {// Remove from the set if the reference has been cleared.iterator.remove();}}}}return bitmap; }

最后,此方法判斷是否有候選的位圖滿足被inBitmap使用的尺寸標(biāo)準(zhǔn):

static boolean canUseForInBitmap(Bitmap candidate, BitmapFactory.Options targetOptions) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// From Android 4.4 (KitKat) onward we can re-use if the byte size of// the new bitmap is smaller than the reusable bitmap candidate// allocation byte count.int width = targetOptions.outWidth / targetOptions.inSampleSize;int height = targetOptions.outHeight / targetOptions.inSampleSize;int byteCount = width * height * getBytesPerPixel(candidate.getConfig());return byteCount <= candidate.getAllocationByteCount();}// On earlier versions, the dimensions must match exactly and the inSampleSize must be 1return candidate.getWidth() == targetOptions.outWidth&& candidate.getHeight() == targetOptions.outHeight&& targetOptions.inSampleSize == 1; }/*** A helper function to return the byte usage per pixel of a bitmap based on its configuration.*/ static int getBytesPerPixel(Config config) {if (config == Config.ARGB_8888) {return 4;} else if (config == Config.RGB_565) {return 2;} else if (config == Config.ARGB_4444) {return 2;} else if (config == Config.ALPHA_8) {return 1;}return 1; }

后一代碼片段展示了上一代碼片段所調(diào)用的方法。它尋找一個(gè)存在的位圖并為之設(shè)值。注意此方法只在找到合適的位圖之后才為其設(shè)值(不能假設(shè)總能匹配到合適的位圖)。

1.5 將位圖顯示在用戶界面中

此節(jié)將綜合前幾節(jié)內(nèi)容,展示用后臺線程和位圖緩存來將位圖載入到像ViewPager和GridView的組件中。

此節(jié)將結(jié)合前幾節(jié)的內(nèi)容,展示如何用后臺線程和位圖緩存將多張位圖載入ViewPager和GridView組件中,并處理并發(fā)和配置改變的情況。

(1) 將位圖載入ViewPager
用掃擊視圖模式(swipe view pattern)來導(dǎo)航圖片畫廊細(xì)節(jié)是一個(gè)不錯(cuò)的方法。可以用PagerAdapter支持的ViewPager來實(shí)現(xiàn)此模式。然而,更加適合的支持適配器是FragmentStatePagerAdapter的子類,此類能夠根據(jù)視圖從屏幕上消失與否的情況自動(dòng)銷毀和保存在ViewPager中的Fragments,并能夠保持內(nèi)存使用量不上升。

注:如果只有少量的圖片并能夠確保它們不會超過應(yīng)用程序的內(nèi)存限制,直接使用PagerAdapter或FragmentPagerAdapter可能會更加適合。

以下代碼實(shí)現(xiàn)了ViewPager和其ImageView子視圖。主活動(dòng)持有此ViewPager和相應(yīng)的適配器:

public class ImageDetailActivity extends FragmentActivity {public static final String EXTRA_IMAGE = "extra_image";private ImagePagerAdapter mAdapter;private ViewPager mPager;// A static dataset to back the ViewPager adapterpublic final static Integer[] imageResIds = new Integer[] {R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.image_detail_pager); // Contains just a ViewPagermAdapter = new ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length);mPager = (ViewPager) findViewById(R.id.pager);mPager.setAdapter(mAdapter);}public static class ImagePagerAdapter extends FragmentStatePagerAdapter {private final int mSize;public ImagePagerAdapter(FragmentManager fm, int size) {super(fm);mSize = size;}@Overridepublic int getCount() {return mSize;}@Overridepublic Fragment getItem(int position) {return ImageDetailFragment.newInstance(position);}} }

以下代碼實(shí)現(xiàn)Fragment持ImageView子視圖的細(xì)節(jié)。這似乎是一種完美的方法,您能看出此種方法的缺陷么?怎么提升?

public class ImageDetailFragment extends Fragment {private static final String IMAGE_DATA_EXTRA = "resId";private int mImageNum;private ImageView mImageView;static ImageDetailFragment newInstance(int imageNum) {final ImageDetailFragment f = new ImageDetailFragment();final Bundle args = new Bundle();args.putInt(IMAGE_DATA_EXTRA, imageNum);f.setArguments(args);return f;}// Empty constructor, required as per Fragment docspublic ImageDetailFragment() {}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1;}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {// image_detail_fragment.xml contains just an ImageViewfinal View v = inflater.inflate(R.layout.image_detail_fragment, container, false);mImageView = (ImageView) v.findViewById(R.id.imageView);return v;}@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);final int resId = ImageDetailActivity.imageResIds[mImageNum];mImageView.setImageResource(resId); // Load image into ImageView} }

希望您能夠注意這個(gè)問題:讀圖片的操作在用戶界面線程中實(shí)現(xiàn),這可能會讓引用程序掛起從而不得不強(qiáng)制關(guān)閉應(yīng)用程序。使用不要在用戶界面線程處理位圖一節(jié)中提到的AsyncTask,此方法能夠在后臺線程中載入和處理圖片。

public class ImageDetailActivity extends FragmentActivity {...public void loadBitmap(int resId, ImageView imageView) {mImageView.setImageResource(R.drawable.image_placeholder);BitmapWorkerTask task = new BitmapWorkerTask(mImageView);task.execute(resId);}... // include BitmapWorkerTask class }public class ImageDetailFragment extends Fragment {...@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);if (ImageDetailActivity.class.isInstance(getActivity())) {final int resId = ImageDetailActivity.imageResIds[mImageNum];// Call out to ImageDetailActivity to load the bitmap in a background thread((ImageDetailActivity) getActivity()).loadBitmap(resId, mImageView);}} }

任何其它的圖片處理(如改變圖片尺寸或從網(wǎng)絡(luò)提取圖片)可以在不影響主用戶界面的BitmapWorkerTask中執(zhí)行。如果后臺線程的操作比從硬盤中載入圖片的操作還多,那么像緩存位圖一節(jié)描述的添加內(nèi)存/硬盤緩沖區(qū)也是有好處的。以下代碼是內(nèi)存緩沖區(qū)的另外的一些修改:

public class ImageDetailActivity extends FragmentActivity {...private LruCache<String, Bitmap> mMemoryCache;@Overridepublic void onCreate(Bundle savedInstanceState) {...// initialize LruCache as per Use a Memory Cache section}public void loadBitmap(int resId, ImageView imageView) {final String imageKey = String.valueOf(resId);final Bitmap bitmap = mMemoryCache.get(imageKey);if (bitmap != null) {mImageView.setImageBitmap(bitmap);} else {mImageView.setImageResource(R.drawable.image_placeholder);BitmapWorkerTask task = new BitmapWorkerTask(mImageView);task.execute(resId);}}... // include updated BitmapWorkerTask from Use a Memory Cache section }

將這些代碼片段整合到一起就能夠得到一個(gè)具響應(yīng)性的、具圖片載入時(shí)最小延遲的ViewPager的實(shí)現(xiàn),并且因?yàn)楹笈_線程處理圖片,所以圖片數(shù)量不會(明顯)影響主用戶界面的執(zhí)行。

(2) 將位圖載入GridView
網(wǎng)格列表構(gòu)建模塊(grid list building block )對顯示圖片數(shù)據(jù)集及其有用,用GridView組件可以實(shí)現(xiàn)網(wǎng)格列表構(gòu)建模塊,GridView組件可以在同一時(shí)間顯示許多圖片,如果用戶滑動(dòng)GridView的滾動(dòng)條就需要做更多的準(zhǔn)備來實(shí)現(xiàn)GridView中的圖片的顯示。在實(shí)現(xiàn)此種類型的控制時(shí),必須確保用戶界面的流暢性、內(nèi)存余量充足、正確地處理并發(fā)(GridView會重復(fù)利用組件來顯示子視圖)。

作為開始,先貼出在Fragment中的擁有ImageView子視圖的GridView的標(biāo)準(zhǔn)實(shí)現(xiàn)。同理,這看起來也已經(jīng)比較完美了,但怎么做能將此做的更好?

public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {private ImageAdapter mAdapter;// A static dataset to back the GridView adapterpublic final static Integer[] imageResIds = new Integer[] {R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};// Empty constructor as per Fragment docspublic ImageGridFragment() {}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mAdapter = new ImageAdapter(getActivity());}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {final View v = inflater.inflate(R.layout.image_grid_fragment, container, false);final GridView mGridView = (GridView) v.findViewById(R.id.gridView);mGridView.setAdapter(mAdapter);mGridView.setOnItemClickListener(this);return v;}@Overridepublic void onItemClick(AdapterView<?> parent, View v, int position, long id) {final Intent i = new Intent(getActivity(), ImageDetailActivity.class);i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position);startActivity(i);}private class ImageAdapter extends BaseAdapter {private final Context mContext;public ImageAdapter(Context context) {super();mContext = context;}@Overridepublic int getCount() {return imageResIds.length;}@Overridepublic Object getItem(int position) {return imageResIds[position];}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup container) {ImageView imageView;if (convertView == null) { // if it's not recycled, initialize some attributesimageView = new ImageView(mContext);imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);imageView.setLayoutParams(new GridView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));} else {imageView = (ImageView) convertView;}imageView.setImageResource(imageResIds[position]); // Load image into ImageViewreturn imageView;}} }

問題在于,將這個(gè)過程的實(shí)現(xiàn)放在了用戶界面線程中。在圖片量較小時(shí),此代碼能夠正常工作。如果有更多的圖片參與,那么用戶界面可能會被掛起。

可以使用上一節(jié)使用的異步和緩存的方法來解決這個(gè)問題。然而,咱還需要為GridView考慮并發(fā)問題。為解決此問題,用“不要在用戶界面處理位圖”一節(jié)中介紹的技術(shù):

public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {...private class ImageAdapter extends BaseAdapter {...@Overridepublic View getView(int position, View convertView, ViewGroup container) {...loadBitmap(imageResIds[position], imageView)return imageView;}}public void loadBitmap(int resId, ImageView imageView) {if (cancelPotentialWork(resId, imageView)) {final BitmapWorkerTask task = new BitmapWorkerTask(imageView);final AsyncDrawable asyncDrawable =new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);imageView.setImageDrawable(asyncDrawable);task.execute(resId);}}static class AsyncDrawable extends BitmapDrawable {private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;public AsyncDrawable(Resources res, Bitmap bitmap,BitmapWorkerTask bitmapWorkerTask) {super(res, bitmap);bitmapWorkerTaskReference =new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);}public BitmapWorkerTask getBitmapWorkerTask() {return bitmapWorkerTaskReference.get();}}public static boolean cancelPotentialWork(int data, ImageView imageView) {final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);if (bitmapWorkerTask != null) {final int bitmapData = bitmapWorkerTask.data;if (bitmapData != data) {// Cancel previous taskbitmapWorkerTask.cancel(true);} else {// The same work is already in progressreturn false;}}// No task associated with the ImageView, or an existing task was cancelledreturn true;}private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {if (imageView != null) {final Drawable drawable = imageView.getDrawable();if (drawable instanceof AsyncDrawable) {final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;return asyncDrawable.getBitmapWorkerTask();}}return null;}... // include updated BitmapWorkerTask class

注:此代碼同樣適用于ListView。

這種實(shí)現(xiàn)能夠靈活的處理圖片且不會影響用戶界面的流暢性。在后臺任務(wù)中可以從網(wǎng)絡(luò)載入圖片也可以調(diào)整大型的數(shù)字圖片并且圖片呈現(xiàn)的速度極快。

完整的樣例代碼和其它方面的討論,請參看本節(jié)的樣例應(yīng)用程序。

2. 用OpenGL ES顯示圖形

在安卓應(yīng)用程序框架中如何創(chuàng)建OpenGL圖形,如何響應(yīng)用戶的點(diǎn)擊輸入。

安卓框架提供了許多標(biāo)準(zhǔn)的工具來創(chuàng)建具有吸引力、功能性的圖形用戶界面。然而,如果想要更多地控制應(yīng)用程序去繪制屏幕,或者要往屏幕上繪制三維圖形,就得使用不同的工具。由安卓框架提供的OpenGL ES APIs提供了顯示高端動(dòng)畫圖形的功能(只有您想不到,無做不到),并且還能夠讓您收益于安卓設(shè)備上的圖形處理單元(GPUs)加速處理圖形的好處。

此部分內(nèi)容將帶您使用OpenGL來開發(fā)一個(gè)基本的應(yīng)用程序,包括組織、繪制對象、移動(dòng)繪制的元素以及響應(yīng)用戶的觸摸輸入。

這里的代碼樣例使用的OpenGL ES 2.0 APIs,針對目前的安卓設(shè)備,推薦大家使用此版本的API。更多關(guān)于OpenGL ES版本的信息,見OpenGL開發(fā)手冊。

注:不要將OpenGL ES 1.x API和OpenGL ES 2.0混淆!這兩種APIs不能互換使用,一起使用它們會導(dǎo)致開發(fā)者累覺不愛。

2.1 構(gòu)建一個(gè)OpenGL ES 環(huán)境

學(xué)習(xí)如何建立一個(gè)可以繪制OpenGL圖形的應(yīng)用程序。

為在應(yīng)用程序中使用OpenGL ES繪制圖形,必須實(shí)現(xiàn)它們的視圖容器。一種實(shí)現(xiàn)視圖容器更直接的方式是實(shí)現(xiàn)GLSurfaceView和FLSurfaceView.Render。GLSurfaceView是用OpenGL繪制圖形的容器,FLSurfaceView.Render控制在視圖中的繪制內(nèi)容。更多關(guān)于兩個(gè)類的信息見OpenGL ES開發(fā)手冊。

GLSurfaceView只是將OpenGL ES圖形結(jié)合到應(yīng)用程序中的一種方法。對于全屏或接近全屏的圖形顯示,此方法是合適的選擇。若開發(fā)者只是想將OpenGL ES圖形作為布局中的一小部分,那么應(yīng)該考慮下TextureView。其實(shí),都可以使用GLSurfaceView來實(shí)現(xiàn),只是此種方法需要更多的代碼來實(shí)現(xiàn)。

此節(jié)將解釋在簡單的應(yīng)用程序活動(dòng)中如何完成的GLSurfaceView和FLSurfaceView.Render的最小實(shí)現(xiàn)。

(1) 在清單文件中聲明OpenGL ES
欲在應(yīng)用程序中使用OpenGL ES 2.0 API,必須在清單文件中作如下聲明:

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

如果應(yīng)用程序使用紋理壓縮,必須聲明應(yīng)用程序所支持的壓縮格式,這樣就只在兼容的設(shè)備上安裝:

<supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" /> <supports-gl-texture android:name="GL_OES_compressed_paletted_texture" />

更多關(guān)于紋理壓縮的格式,見OpenGL開發(fā)手冊。

(2) 為OpenGL ES圖形創(chuàng)建活動(dòng)
使用OpenGL ES的安卓應(yīng)用程序跟其它應(yīng)用程序一樣有用戶界面所對應(yīng)的活動(dòng)。主要不同在于往活動(dòng)的布局文件中所添加的東西。在其它的應(yīng)用程序中的布局文件中往往可能包含TexView、Button或ListView,在使用OpenGL ES的應(yīng)用程序中,還會往布局文件中添加GLSurfaceView。

以下代碼樣例是用GLSurfaceView作為原始視圖的活動(dòng)的一個(gè)最小實(shí)現(xiàn):

public class OpenGLES20Activity extends Activity {private GLSurfaceView mGLView;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// Create a GLSurfaceView instance and set it// as the ContentView for this Activity.mGLView = new MyGLSurfaceView(this);setContentView(mGLView);} }

注:OpenGL ES 2.0需要安卓2.2(API level 8)或更高的版本,所以要確保安卓工程的API目標(biāo)。

(3) 構(gòu)建GLSurfaceView對象
GLSurfaceView是一個(gè)可以繪制OpenGL ES圖形的特殊視圖。此視圖本身不會為繪圖做太多。實(shí)際控制繪制對象的是設(shè)置在此視圖上的GLSurfaceView.Renderer。實(shí)際上,創(chuàng)建此對象的代碼量很少,您可能想跳過擴(kuò)展代碼而只創(chuàng)建一個(gè)GLSurfaceView實(shí)例,但不要如此。需要擴(kuò)展此類來獲取觸摸事件,此在“響應(yīng)屏幕觸摸”一節(jié)中講述過。

實(shí)現(xiàn)GLSurfaceView的必要的代碼很少,所以能夠快速實(shí)現(xiàn),它通常作為使用它的活動(dòng)的內(nèi)部類來實(shí)現(xiàn):

class MyGLSurfaceView extends GLSurfaceView {private final MyGLRenderer mRenderer;public MyGLSurfaceView(Context context){super(context);// Create an OpenGL ES 2.0 contextsetEGLContextClientVersion(2);mRenderer = new MyGLRenderer();// Set the Renderer for drawing on the GLSurfaceViewsetRenderer(mRenderer);} }

除了GLSurfaceView實(shí)現(xiàn)外,另外一種方法是當(dāng)繪制內(nèi)容有改變時(shí)用GLSurfaceView.RENDERMODE_WHEN_DIRTY將渲染模式設(shè)置只繪制視圖。

// Render the view only when there is a change in the drawing data setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

此方法可以防止在確切調(diào)用requestRender()時(shí)GLSurfaceView的重復(fù)繪制,在本樣例程序中這是一種更為高笑的方法。

(4) 構(gòu)建渲染器(Renderer)類
在應(yīng)用程序中實(shí)現(xiàn)GLSurfaceView.Renderer或renderer類使得使用OpenGL ES變得有趣。此類控制往與此類關(guān)聯(lián)的GLSurfaceView中的繪制內(nèi)容。渲染器類中有3個(gè)方法會被安卓系統(tǒng)調(diào)用以推測出怎么繪制GLSurfaceView以及往其中繪制的內(nèi)容:
- onSurfaceCreate() - 被調(diào)用一次,用來設(shè)置視圖的OpenGL ES環(huán)境。
- onDrawFrame() - 在每次繪制重新繪制視圖時(shí)都會被調(diào)用。
- onSurfaceChanged() - 在視圖形狀改變時(shí)會被調(diào)用,如當(dāng)設(shè)備屏幕方向改變時(shí)。

以下是一個(gè)OpenGL ES渲染器的一個(gè)非常基本的實(shí)現(xiàn),它為GLSurfaceView繪制一個(gè)黑色的背景:

public class MyGLRenderer implements GLSurfaceView.Renderer {public void onSurfaceCreated(GL10 unused, EGLConfig config) {// Set the background frame colorGLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);}public void onDrawFrame(GL10 unused) {// Redraw background colorGLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);}public void onSurfaceChanged(GL10 unused, int width, int height) {GLES20.glViewport(0, 0, width, height);} }

以上的代碼示例創(chuàng)建了一個(gè)用OpenGL來簡單的顯示一個(gè)黑色背景的安卓應(yīng)用程序。并未做一些更有趣的事情,不過現(xiàn)在您已經(jīng)具有了OpenGL的基礎(chǔ),那么您就可以開始用OpenGL來開始繪制圖形元素了。

注:在使用OpenGL ES 2.0 APIs時(shí),您可能想知道為什么這些方法需要GL10的參數(shù)。這些方法簽名只是為能夠在2.0 API中重用以保障安卓代碼礦建的簡單性。

如果您熟悉OpenGL ES APIs,就可以在應(yīng)用程序中設(shè)置OpenGL ES環(huán)境并開始繪制圖形。然而,如果您還需要更多的關(guān)于OpenGL的信息幫助,請繼續(xù)往后看。

2.2 定義形狀

學(xué)習(xí)如何定義形狀,了解為什么需要知道圖形輪廓(faces and winding)。

創(chuàng)建高端圖形杰作的第一步是在OpenGL ES視圖的上下文中定義被畫的形狀。若不知OpenGL ES定義圖形對象的步驟,那么用OpenGL ES繪制圖形就會有些困難。

此節(jié)解釋“OpenGL ES在安卓設(shè)備屏幕上的坐標(biāo)系”、“定義形狀的基礎(chǔ)”、“形狀面”、“定義三角形或矩形”。

(1) 定義三角形
OpenGL ES運(yùn)行在三維空間坐標(biāo)定義欲繪制的對象。所以,在繪制三角形之前,必須先定義坐標(biāo)。在OpenGL中,一般是通過定義以浮點(diǎn)數(shù)字組成的頂點(diǎn)數(shù)組來定義坐標(biāo)。欲達(dá)最大效率,需要將這些坐標(biāo)寫進(jìn)ByteBuffer,然后將其中的內(nèi)容傳遞給OpenGL ES圖形管道以作相應(yīng)處理:

public class Triangle {private FloatBuffer vertexBuffer;// number of coordinates per vertex in this arraystatic final int COORDS_PER_VERTEX = 3;static float triangleCoords[] = { // in counterclockwise order:0.0f, 0.622008459f, 0.0f, // top-0.5f, -0.311004243f, 0.0f, // bottom left0.5f, -0.311004243f, 0.0f // bottom right};// Set color with red, green, blue and alpha (opacity) valuesfloat color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };public Triangle() {// initialize vertex byte buffer for shape coordinatesByteBuffer bb = ByteBuffer.allocateDirect(// (number of coordinate values * 4 bytes per float)triangleCoords.length * 4);// use the device hardware's native byte orderbb.order(ByteOrder.nativeOrder());// create a floating point buffer from the ByteBuffervertexBuffer = bb.asFloatBuffer();// add the coordinates to the FloatBuffervertexBuffer.put(triangleCoords);// set the buffer to read the first coordinatevertexBuffer.position(0);} }

默認(rèn)情況下,OpenGL ES假設(shè)坐標(biāo)系的0,0,0對應(yīng)GLSurfaceView框架的中心,[1,1,0]為框架的右上角,[-1,-1,0]對應(yīng)框架的左下角。欲看此坐標(biāo)系的圖解,見OpenGL ES開發(fā)手冊。

注意形狀的坐標(biāo)系是以逆時(shí)針為順序。繪制的順序非常重要因?yàn)樗x那一邊是形狀的正面(正面會被繪制)以及哪一邊是形狀的背面(根據(jù)OpenGL ES剔除的特性,背面不會被繪制)。更多關(guān)于面(facing)和剔除(culling)見OpenGL ES 開發(fā)手冊。

(2) 定義矩形
在OpenGL中定義三角形相當(dāng)簡單,但當(dāng)定義圖形變得稍加復(fù)雜時(shí)應(yīng)該怎么定義?比如如,一個(gè)矩形。有幾種方式可以定義矩形,在OpenGL定義矩形最為典型的方式是定義兩個(gè)三角形來形成矩形。

圖1. 用兩個(gè)三角形繪制矩形

需要以逆時(shí)針的順序來定義組成矩形的兩個(gè)三角形,并將坐標(biāo) 值都保存到ByteBuffer中。為避免重復(fù)定義三角形所共享頂點(diǎn),需要用繪制清單來告知OpenGL ES圖形管道怎么繪制這些頂點(diǎn)。以下是繪制矩形的代碼:

public class Square {private FloatBuffer vertexBuffer;private ShortBuffer drawListBuffer;// number of coordinates per vertex in this arraystatic final int COORDS_PER_VERTEX = 3;static float squareCoords[] = {-0.5f, 0.5f, 0.0f, // top left-0.5f, -0.5f, 0.0f, // bottom left0.5f, -0.5f, 0.0f, // bottom right0.5f, 0.5f, 0.0f }; // top rightprivate short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw verticespublic Square() {// initialize vertex byte buffer for shape coordinatesByteBuffer bb = ByteBuffer.allocateDirect(// (# of coordinate values * 4 bytes per float)squareCoords.length * 4);bb.order(ByteOrder.nativeOrder());vertexBuffer = bb.asFloatBuffer();vertexBuffer.put(squareCoords);vertexBuffer.position(0);// initialize byte buffer for the draw listByteBuffer dlb = ByteBuffer.allocateDirect(// (# of coordinate values * 2 bytes per short)drawOrder.length * 2);dlb.order(ByteOrder.nativeOrder());drawListBuffer = dlb.asShortBuffer();drawListBuffer.put(drawOrder);drawListBuffer.position(0);} }

此例給了一個(gè)怎么用OpenGL來創(chuàng)建稍微復(fù)雜圖形的小窺。通常來講,都是使用三角形來繪制對象。在下一節(jié)中,將會介紹如何將這些形狀繪制到屏幕上。

2015.11.15

2.3 繪制形狀

學(xué)習(xí)在應(yīng)用程序中如何繪制OpenGL 形狀。

在用OpenGL定義形狀后,就可以繪制它們了。用OpenGL ES 2.0繪制推行可能比您的想象還要多一些代碼,因?yàn)檫@些API對圖形渲染管道提供了極大的控制。

此節(jié)介紹怎么繪制前一節(jié)用OpenGL ES 2.0 API所定義的形狀。

(1) 初始化形狀
在作繪制之前,必須初始化并載入打算繪制的形狀。除非程序中使用的形狀的結(jié)構(gòu)(坐標(biāo)系)在執(zhí)行過程中改變,否則都應(yīng)該在渲染器的onSurfaceCreated()方法中為形狀的內(nèi)存和效率執(zhí)行作初始化工作。

public class MyGLRenderer implements GLSurfaceView.Renderer {...private Triangle mTriangle;private Square mSquare;public void onSurfaceCreated(GL10 unused, EGLConfig config) {...// initialize a trianglemTriangle = new Triangle();// initialize a squaremSquare = new Square();}... }

(2) 繪制形狀
繪制用OpenGL ES 2.0定義的形狀需要大量代碼,因?yàn)楸仨殲閳D形渲染管道提供許多細(xì)節(jié)。尤其是需要定義以下介個(gè)方面內(nèi)容:
- 頂點(diǎn)著色(Vertex Shader) - 渲染形狀頂點(diǎn)的OpenGL ES 圖形代碼。
- 片段著色(Fragment Shader) - 用顏色或紋理來渲染形狀各面的OpenGL ES代碼。
- 程序(Program) - 用包含著色的OpenGL ES 對象來繪制一個(gè)或多個(gè)形狀。

至少需要一個(gè)頂點(diǎn)著色來繪制形狀一個(gè)片段著色來為形狀著色。這些著色器必須被編譯然后增添到OpenGL ES程序中,著色器會被用來繪制形狀。以下代碼描述在三角形類中如何定義基本的著色器來繪制形狀:

public class Triangle {private final String vertexShaderCode ="attribute vec4 vPosition;" +"void main() {" +" gl_Position = vPosition;" +"}";private final String fragmentShaderCode ="precision mediump float;" +"uniform vec4 vColor;" +"void main() {" +" gl_FragColor = vColor;" +"}";... }

著色器使用的是OpenGL的著色語言(GLSL),著色器代碼必須用OpenGL ES環(huán)境預(yù)編譯。在渲染器類中創(chuàng)建一個(gè)方法來編譯以上著色器的代碼:

public static int loadShader(int type, String shaderCode){// create a vertex shader type (GLES20.GL_VERTEX_SHADER)// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)int shader = GLES20.glCreateShader(type);// add the source code to the shader and compile itGLES20.glShaderSource(shader, shaderCode);GLES20.glCompileShader(shader);return shader; }

欲繪制形狀,必須編譯著色器代碼,然后將編譯后的代碼添加到OpenGL ES程序?qū)ο笾性龠B接程序。在繪制對象的構(gòu)造函數(shù)中完成這個(gè)過程,這樣此過程就只會被執(zhí)行一次。

注:編譯OpenGL ES著色器和連接程序?qū)τ贑PU周期來和處理時(shí)間來說是比較耗時(shí)的操作,所以要避免此操作被執(zhí)行多次。如果在運(yùn)行時(shí)不知著色器的內(nèi)容,應(yīng)該構(gòu)建(編譯)代碼這樣代碼只會被構(gòu)建一次且可緩存供以后使用:

public class Triangle() {...private final int mProgram;public Triangle() {...int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode);// create empty OpenGL ES ProgrammProgram = GLES20.glCreateProgram();// add the vertex shader to programGLES20.glAttachShader(mProgram, vertexShader);// add the fragment shader to programGLES20.glAttachShader(mProgram, fragmentShader);// creates OpenGL ES program executablesGLES20.glLinkProgram(mProgram);} }

此時(shí),到了可以調(diào)用方法來繪制形狀的時(shí)候了。用OpenGL ES需要用幾個(gè)參數(shù)來告知渲染管道將繪制的內(nèi)容并如何繪制它們。因?yàn)槔L制過程由形狀決定,所以在圖形類中包含圖形的繪制邏輯是個(gè)不錯(cuò)的主意。

創(chuàng)建一個(gè)draw()方法來繪制圖形。以下代碼設(shè)置了位置和顏色值到形狀的頂點(diǎn)著色器和片段著色器,并調(diào)用繪制方法來繪制形狀。

private int mPositionHandle; private int mColorHandle;private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX; private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertexpublic void draw() {// Add program to OpenGL ES environmentGLES20.glUseProgram(mProgram);// get handle to vertex shader's vPosition membermPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");// Enable a handle to the triangle verticesGLES20.glEnableVertexAttribArray(mPositionHandle);// Prepare the triangle coordinate dataGLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,GLES20.GL_FLOAT, false,vertexStride, vertexBuffer);// get handle to fragment shader's vColor membermColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");// Set color for drawing the triangleGLES20.glUniform4fv(mColorHandle, 1, color, 0);// Draw the triangleGLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);// Disable vertex arrayGLES20.glDisableVertexAttribArray(mPositionHandle); }

只要以上代碼全部就位,繪制此對象就只需要在渲染器的onDrawFrame()方法中調(diào)用draw()方法了:

public void onDrawFrame(GL10 unused) {...mTriangle.draw(); }

運(yùn)行應(yīng)用程序,運(yùn)行結(jié)果如下:

圖1. 無投影或相機(jī)視圖下繪制的三角形

在以上代碼樣例中存在幾個(gè)問題。第一,此運(yùn)行結(jié)果不會給人留下深刻印象。第二,當(dāng)改變設(shè)備屏幕方向時(shí)三角形會有些變形。會變形的原因時(shí)對象的頂點(diǎn)沒有隨著屏幕變化而變化。可以通過下一節(jié)介紹的投影和相機(jī)視圖來解決這個(gè)問題。

第三,圖中的三角形是固定不動(dòng)的,這顯得有些無聊。在“增添運(yùn)動(dòng)”這一節(jié)中會使用OpenGL ES圖形管道來讓圖像旋轉(zhuǎn)以讓所繪的圖形看起來更有趣。

2.4 請求投影和相機(jī)視圖

學(xué)習(xí)如何使用投影和相機(jī)視角獲取所繪對象的新的視角。

在OpenGL ES環(huán)境中,投影和相機(jī)視圖以一種更接近在現(xiàn)實(shí)中用眼睛看到物理對象那般呈現(xiàn)圖片。這種模擬實(shí)際視圖的方式是在繪制物體的坐標(biāo)系中的通過數(shù)學(xué)變換實(shí)現(xiàn)的:
- 投影 - 此變換通過調(diào)整繪制對象坐標(biāo)的寬度和高度來展現(xiàn)繪制圖像。無此變換的計(jì)算,因視圖窗口的比例的不等從而用OpenGL ES繪制的對象是傾斜的。當(dāng)OpenGL視圖比例確立或渲染器中onSurfaceChanged()方法中的視圖比例改變時(shí),投影變換將會重新計(jì)算。更多關(guān)于OpenGL ES的投影和坐標(biāo)映射,見Mapping Coordinates for Drawn Objects.。
- 相機(jī)視圖 - 此變換調(diào)整繪制對象坐標(biāo)的虛擬相機(jī)位置。OpenGL ES并未定義實(shí)際的相機(jī)對象,它是通過變換繪制對象的顯示而提供了工具方法來模擬相機(jī)。當(dāng)確立GLSurfaceView或有基于用戶或應(yīng)用程序的動(dòng)態(tài)改變時(shí),相機(jī)視圖可能只會被計(jì)算一次。

此節(jié)描述如何創(chuàng)建投影和相機(jī)以及如何在GLSurfaceView中應(yīng)用它們。

(1) 定義投影
投影變換的數(shù)據(jù)在GLSurfaceView.Renderer類中的onSurfaceChanged()方法中計(jì)算。以下樣例代碼根據(jù)GLSurfaceView的高度和寬度用Matrix.frustumM()方法計(jì)算投影變換的Matrix:

// mMVPMatrix is an abbreviation for "Model View Projection Matrix" private final float[] mMVPMatrix = new float[16]; private final float[] mProjectionMatrix = new float[16]; private final float[] mViewMatrix = new float[16];@Override public void onSurfaceChanged(GL10 unused, int width, int height) {GLES20.glViewport(0, 0, width, height);float ratio = (float) width / height;// this projection matrix is applied to object coordinates// in the onDrawFrame() methodMatrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7); }

此段代碼計(jì)算了投影矩陣mProjectionMatrix,相機(jī)視圖將在onDrawFrame()方法中使用此矩陣,此將在下一節(jié)中介紹。

注:只對繪制對象應(yīng)用投影一般會導(dǎo)致圖形消失。通常,為了將圖形重新顯示在屏幕上還需要使用相機(jī)視圖。

(2) 定義相機(jī)視圖
為繪制對象增加相機(jī)視圖變換方才算完成了圖形的變換。在以下代碼示例中,在Matrix.setLookATM()方法中完成相機(jī)試圖變換并結(jié)合之前的投影變換矩陣。再將兩種變換結(jié)合得到矩陣傳遞給繪制對象:

@Override public void onDrawFrame(GL10 unused) {...// Set the camera position (View matrix)Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);// Calculate the projection and view transformationMatrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);// Draw shapemTriangle.draw(mMVPMatrix); }

(3) 請求投影和相機(jī)變換
欲結(jié)合投影和相機(jī)視圖變換矩陣,首先需要在之前的三角形類中定義頂點(diǎn)著色器矩陣:

public class Triangle {private final String vertexShaderCode =// This matrix member variable provides a hook to manipulate// the coordinates of the objects that use this vertex shader"uniform mat4 uMVPMatrix;" +"attribute vec4 vPosition;" +"void main() {" +// the matrix must be included as a modifier of gl_Position// Note that the uMVPMatrix factor *must be first* in order// for the matrix multiplication product to be correct." gl_Position = uMVPMatrix * vPosition;" +"}";// Use to access and set the view transformationprivate int mMVPMatrixHandle;... }

然后,修改繪制對象的draw()方法以接收二者變換的矩陣并將此矩陣應(yīng)用到圖形:

public void draw(float[] mvpMatrix) { // pass in the calculated transformation matrix...// get handle to shape's transformation matrixmMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");// Pass the projection and view transformation to the shaderGLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);// Draw the triangleGLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);// Disable vertex arrayGLES20.glDisableVertexAttribArray(mPositionHandle); }

一旦正確和應(yīng)用了投影和相機(jī)視圖變換,以正確比例被繪制的圖形對象應(yīng)該如下圖所示:

圖1. 用投影和相機(jī)視圖變換的三角形繪制

至此,應(yīng)用程序中已經(jīng)用正確的比例繪制圖形了,該往形狀增添動(dòng)畫了。

2.5 增加運(yùn)動(dòng)

學(xué)習(xí)如何用OpenGL來實(shí)現(xiàn)所繪對象基本的移動(dòng)和動(dòng)畫。

將對象繪制在屏幕之上只是OpenGL最基本的特定,安卓其它的圖形框架類如Canvas及Drawable也能夠完成此項(xiàng)工作。OpenGL ES還為圖形提供了移動(dòng)、變換圖形到三維空間以及創(chuàng)造令人信服的用戶體驗(yàn)的功能。

此節(jié)將繼續(xù)學(xué)習(xí)OpenGL ES來通過旋轉(zhuǎn)的方式移動(dòng)圖形。

(1) 旋轉(zhuǎn)圖形
用OpenGL ES 2.0來選中繪制對象比較簡單。在渲染器中創(chuàng)建一個(gè)轉(zhuǎn)換矩陣(旋轉(zhuǎn)矩陣)并將其跟投影和相機(jī)視圖變換矩陣結(jié)合到一塊:

private float[] mRotationMatrix = new float[16]; public void onDrawFrame(GL10 gl) {float[] scratch = new float[16];...// Create a rotation transformation for the trianglelong time = SystemClock.uptimeMillis() % 4000L;float angle = 0.090f * ((int) time);Matrix.setRotateM(mRotationMatrix, 0, angle, 0, 0, -1.0f);// Combine the rotation matrix with the projection and camera view// Note that the mMVPMatrix factor *must be first* in order// for the matrix multiplication product to be correct.Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);// Draw trianglemTriangle.draw(scratch); }

如果做了這些改變后三角形仍舊還沒有旋轉(zhuǎn),確保對GLSurfaceView.RENDERMODE_WHEN_DIRTY進(jìn)行了注釋,此內(nèi)容在下一節(jié)討論。

(2) 啟用連續(xù)渲染
如果您已孜孜不倦地昨晚了樣例代碼中的所有內(nèi)容,確保注釋了設(shè)置渲染模式為只在臟(dirty)才繪制的一行代碼,否則OpenGL只做一次旋轉(zhuǎn)然后就等待調(diào)用GLSurfaceView容器中的requestRender()方法:

public MyGLSurfaceView(Context context) {...// Render the view only when there is a change in the drawing data.// To allow the triangle to rotate automatically, this line is commented out://setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); }

除非在無用戶交互的情況下對象還有轉(zhuǎn)動(dòng),否則應(yīng)該將此語句的注釋去掉。做好取消此語句注釋的準(zhǔn)備,因?yàn)橄乱还?jié)將會在程序中使用此語句。

2.6 響應(yīng)觸摸事件

學(xué)習(xí)怎么和OpenGL圖形實(shí)現(xiàn)基本的互動(dòng)。

移動(dòng)預(yù)先設(shè)定程序中的對象是有用的,如此會得到用戶的更多關(guān)注。但要是想讓OpenGL ES圖形能和用戶交互又改怎么樣做呢?讓OpenGL ES應(yīng)用程序能夠和用戶交互的關(guān)鍵是重寫GLSurfaceView中的onTouchEvent()來監(jiān)聽觸摸事件。

此節(jié)介紹如何監(jiān)聽用戶的觸摸事件以讓用戶旋轉(zhuǎn)OpenGL ES對象。

(1) 設(shè)置觸摸監(jiān)聽器
欲使OpenGL ES應(yīng)用程序響應(yīng)觸摸事件,必須實(shí)現(xiàn)GLSurfaceView類中的onTouchEvent()方法。以下實(shí)現(xiàn)的代碼展示了怎么監(jiān)聽MotionEvent.ACTION_MOVE事件并將它們轉(zhuǎn)換為形狀旋轉(zhuǎn)的角度。

private final float TOUCH_SCALE_FACTOR = 180.0f / 320; private float mPreviousX; private float mPreviousY;@Override public boolean onTouchEvent(MotionEvent e) {// MotionEvent reports input details from the touch screen// and other input controls. In this case, you are only// interested in events where the touch position changed.float x = e.getX();float y = e.getY();switch (e.getAction()) {case MotionEvent.ACTION_MOVE:float dx = x - mPreviousX;float dy = y - mPreviousY;// reverse direction of rotation above the mid-lineif (y > getHeight() / 2) {dx = dx * -1 ;}// reverse direction of rotation to left of the mid-lineif (x < getWidth() / 2) {dy = dy * -1 ;}mRenderer.setAngle(mRenderer.getAngle() +((dx + dy) * TOUCH_SCALE_FACTOR));requestRender();}mPreviousX = x;mPreviousY = y;return true; }

注意在計(jì)算旋轉(zhuǎn)角度后,此方法調(diào)用了requestRender()來告知渲染器該渲染框架了。此方法在此樣例中最為有效,因?yàn)榭蚣懿恍枰禺?#xff0c;除非旋轉(zhuǎn)有變。然而,它不會影響效率除非用setRenderMode()方法來設(shè)置渲染器只進(jìn)行重繪制操作,所以確保以下這行代碼沒有被注釋:

public MyGLSurfaceView(Context context) {...// Render the view only when there is a change in the drawing datasetRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); }

(2) 獲取旋轉(zhuǎn)角度
以上代碼樣例需要通過增加一個(gè)公有的變量來揭露渲染器的旋轉(zhuǎn)角度。由于渲染代碼運(yùn)行于獨(dú)立于用戶主線程的線程中,必須將此變量聲明為volatile。以下代碼聲明了此變量且揭露了獲取和設(shè)置方法:

public class MyGLRenderer implements GLSurfaceView.Renderer {...public volatile float mAngle;public float getAngle() {return mAngle;}public void setAngle(float angle) {mAngle = angle;} }

(3) 應(yīng)用旋轉(zhuǎn)
欲應(yīng)用通過觸摸輸入產(chǎn)生的旋轉(zhuǎn),注釋掉產(chǎn)生角度的代碼并增加mAngle變量,此變量包含了觸摸事件生成的旋轉(zhuǎn)角度:

public void onDrawFrame(GL10 gl) {...float[] scratch = new float[16];// Create a rotation for the triangle// long time = SystemClock.uptimeMillis() % 4000L;// float angle = 0.090f * ((int) time);Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);// Combine the rotation matrix with the projection and camera view// Note that the mMVPMatrix factor *must be first* in order// for the matrix multiplication product to be correct.Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);// Draw trianglemTriangle.draw(scratch); }

當(dāng)完成以上描述的所有步驟后,運(yùn)行程序并用手指再屏幕上滑動(dòng)來選擇三角形,運(yùn)行結(jié)果會類似下圖:

圖1. 觸摸輸入選擇三角形(圓圈表示觸摸位置)

2015.11.16

3. 使用場景和變換來實(shí)現(xiàn)動(dòng)畫視圖

在視圖層次如何用轉(zhuǎn)換來讓動(dòng)畫狀態(tài)改變。

活動(dòng)對應(yīng)的用戶界面會常會因?yàn)轫憫?yīng)用戶輸入或其它事件而變化。例如,包含供用戶輸入查詢內(nèi)容的查詢條在用戶提交后可以隱藏查詢條而呈現(xiàn)查詢結(jié)果。

欲在這些情形下提供視覺上的連續(xù),可以在用戶界面的不同視圖層次中作動(dòng)畫般的改變。這些動(dòng)畫給以用戶動(dòng)作上的響應(yīng)并幫助用戶學(xué)習(xí)應(yīng)用程序是怎么工作的。

安卓包含變換框架,此框架能夠很易在兩個(gè)視圖層次間作動(dòng)畫改變。框架在運(yùn)行時(shí)通過改變視圖的某些特性來動(dòng)畫視圖。框架中既包含針對于常見效果的內(nèi)建動(dòng)畫也允許開發(fā)者自定義動(dòng)畫和變換生命周期回調(diào)方法。

此節(jié)教您使用變換框架內(nèi)的內(nèi)建動(dòng)畫來動(dòng)畫改變兩個(gè)不同視圖層次的視圖。此節(jié)同樣包含如何創(chuàng)建自定義動(dòng)畫。

注:對于在4.0(API level 14)和4.4.2(API level 19)的安卓版本,使用animateLayoutChanges屬性來動(dòng)畫布局。欲獲取更多信息,見Property Animation及Animating Layout Changes。

3.1 變換框架

學(xué)習(xí)變換框架主要的特性和組件。

動(dòng)畫應(yīng)用程序用戶界面不僅是視覺上的呼吁。動(dòng)畫強(qiáng)調(diào)改變且提供了應(yīng)用程序是如何工作的視覺線索。

欲幫助開發(fā)者動(dòng)畫在兩個(gè)視圖層次的改變,安卓提供了變換框架。此框架能應(yīng)用一個(gè)或多個(gè)動(dòng)畫到有改變的層次中的所有視圖之間。

框架有以下特性:
組-級動(dòng)畫
應(yīng)用一個(gè)或多個(gè)動(dòng)畫去影響視圖層次中的所有視圖。

變換-基礎(chǔ)動(dòng)畫動(dòng)畫的運(yùn)行基于視圖特性值改變開始和結(jié)束之間。內(nèi)建動(dòng)畫包含具常見印象的預(yù)定義動(dòng)畫,諸如漸弱或移動(dòng)。資源文件支持從布局資源文件載入視圖層和內(nèi)建動(dòng)畫。生命周期定義回調(diào)方法提供堆動(dòng)畫和層改變處理的更好控制。

(1) 概要
圖1的圖例展示動(dòng)畫是如何提供視覺線索來幫助用戶的。當(dāng)應(yīng)用程序從搜索條屏幕改變到搜索結(jié)果屏幕時(shí),屏幕漸弱不再使用的視圖而漸現(xiàn)幾個(gè)新的視圖。

用戶動(dòng)畫界面:http://developer.android.com/images/transitions/transition_sample_video.mp4
圖1. 視覺線索使用用戶界面動(dòng)畫。點(diǎn)擊設(shè)備屏幕放映動(dòng)畫。

2015.11.16
此動(dòng)畫是使用變換框架的一個(gè)例子。框架動(dòng)畫改變兩個(gè)視圖層次中的所有視圖。一個(gè)視圖層次可以簡單得只有一個(gè)視圖也可以復(fù)雜到像ViewGroup包含復(fù)雜的視圖樹。框架在視圖層的開始和結(jié)束期間通過改變視圖的特性值來動(dòng)畫每個(gè)視圖。

變換框架以并行的方式工作于視圖層和動(dòng)畫。框架的目的是存儲視圖層的狀態(tài),在這些層之間作改變以修改屏幕的顯示,通過存儲和應(yīng)用動(dòng)畫定義進(jìn)行動(dòng)畫改變。

圖2中所示的框圖能夠說明視圖層、框架對象以及動(dòng)畫之間的關(guān)系:

圖2. 變換框架各部分之間的關(guān)系

變換框架為場景、變換以及變換方式提供了抽象的理念。在后續(xù)節(jié)中將會詳細(xì)描述三者。欲使用此框架,在應(yīng)用程序中為計(jì)劃改變的視圖層創(chuàng)建場景。然后,為欲使用的各個(gè)動(dòng)畫創(chuàng)建變換。欲在兩個(gè)視圖層之間開始動(dòng)畫,用變換方法來制定欲使用的變換和結(jié)束場景。此過程在此節(jié)余留部分詳細(xì)講解。

(2) 場景
場景用來存儲視圖層的狀態(tài),包括所有視圖以及它們的特性值。一個(gè)視圖可能是簡單的或是視圖和其子視圖的復(fù)雜的樹視圖。在場景中保存視圖狀態(tài)能夠使得從另外的場景變換到此種狀態(tài)。框架提供了Scene類來呈現(xiàn)場景。

變換框架能夠根據(jù)布局資源文件或代碼中的ViewGroup對象來創(chuàng)建場景。如果動(dòng)態(tài)的創(chuàng)建或運(yùn)行時(shí)修改視圖層,那么在代碼中創(chuàng)建場景會很有用。

在大多數(shù)情況下,不會精確的創(chuàng)建開始場景。如果已經(jīng)應(yīng)用了變換,框架用之前的結(jié)束場景作為后續(xù)變換的開始場景。如果并未應(yīng)用變換,框架將會從屏幕當(dāng)前狀態(tài)收集 的信息。

也可以為場景定義場景自己的動(dòng)作,當(dāng)場景改變時(shí)將會運(yùn)行此些動(dòng)作。例如,在變換場景之后親你管理視圖設(shè)置。

除視圖層和其屬性值之外,場景還存儲父視圖層的引用。根視圖被稱為scene root。改變場景和動(dòng)畫會影響根場景下的場景。

更多關(guān)于創(chuàng)建場景的信息見“創(chuàng)建場景”。

(3) 變換
在變換框架中,動(dòng)畫創(chuàng)造了一系列描述各視圖層在開始和結(jié)束場景之間變化的框架。關(guān)于動(dòng)畫的信息被保存在Transition對象中。用TransitionManager實(shí)例運(yùn)行動(dòng)畫。框架能夠在不用場景之間變換也能夠在同一個(gè)場景的不同狀態(tài)間變換。

框架包含了一套用于常見動(dòng)畫效果的內(nèi)建變換,如漸變和調(diào)整視圖尺寸。也可以用動(dòng)畫框架中的APIs來自定義變換以創(chuàng)建動(dòng)畫效果。變換框架同樣允許聯(lián)合包含內(nèi)建或自定義變換組的變換集中的不同的動(dòng)畫。

變換的生命周期類似活動(dòng)的生命周期,在動(dòng)畫開始和完成期間由框架監(jiān)控生命周期對應(yīng)的變換狀態(tài)。一個(gè)重要的生命周期狀態(tài), 在變換階段可以實(shí)現(xiàn)框架會調(diào)用的回調(diào)方法來調(diào)整用戶界面。

更多關(guān)于變換的信息,見Applying a Transition及Creating Custom Transitions。

(4) 限制
以下理解了變換框架的一些知名的限制:
- 動(dòng)畫應(yīng)用到SurfaceView可能不會正確的顯示。SurfaceView實(shí)例在非用戶界面線程中更新,所以更新可能超出其它視圖的動(dòng)畫的異步范圍。
- 當(dāng)應(yīng)用于TextureView時(shí),一些特殊的變換類型可能不會產(chǎn)生應(yīng)有的動(dòng)畫效果。
- 從AdapterView擴(kuò)展類,如ListView,管理子視圖的方法與變化框架不兼容。如果在AdapterView上實(shí)現(xiàn)動(dòng)畫視圖,設(shè)備顯示可能會被掛起。
- 如果通過動(dòng)畫來調(diào)整TextView的尺寸,在對象尺寸被調(diào)整完成之前文本將會突然跑到一個(gè)新的區(qū)域。欲避免此問題,不要用動(dòng)畫調(diào)整包含文本的視圖。

3.2 創(chuàng)建場景

學(xué)習(xí)如何創(chuàng)建場景來存儲視圖層次的狀態(tài)。

場景存儲視圖層的狀態(tài),包括所有視圖和其特性值。變換框架在開始場景和結(jié)束場景之間運(yùn)行動(dòng)畫。開始場景從用戶當(dāng)前界面的當(dāng)前狀態(tài)獲得。對于結(jié)束場景,框架讓開發(fā)者從布局資源文件或代碼中的一組視圖創(chuàng)建結(jié)束場景。

此節(jié)演示如何在應(yīng)用程序中創(chuàng)建場景以及如何定義場景動(dòng)作。下一節(jié)將演示如何在兩個(gè)場景之間變換。

注:框架能夠在一個(gè)無場景的視圖層中動(dòng)畫改編,在Apply a Transition Without Scenes一節(jié)中已描述過。然而,理解此節(jié)內(nèi)容對于變換來說是必要的。

(1) 從布局資源創(chuàng)建場景
可以根據(jù)布局資源文件直接創(chuàng)建場景。當(dāng)文件中的視圖層大多是靜態(tài)時(shí)可以使用此技術(shù)。場景實(shí)例中的結(jié)果代表視圖層的某時(shí)刻的狀態(tài)。欲改變視圖層,就需要重建場景。框架根據(jù)文件中的整個(gè)視圖層來創(chuàng)建場景;不能只根據(jù)布局文件的某一部分創(chuàng)建場景。

欲根據(jù)布局資源文件創(chuàng)建場景,檢索ViewGroup實(shí)例布局文件的場景根,然后用包含視圖層的布局文件的場景根和資源ID調(diào)用Scene.getSceneForLayout()方法來創(chuàng)建場景。

[1] 為場景定義布局
此節(jié)后續(xù)部分代碼將演示如何用相同的場景根元素來創(chuàng)建兩個(gè)不同的場景。這些代碼片段同時(shí)演示在不用聲明場景彼此的相關(guān)性而載入多個(gè)不相關(guān)的場景對象。

樣例有以下的布局定義組成:
- 活動(dòng)的擁有一個(gè)文本標(biāo)簽和子布局的主布局文件。
- 第一個(gè)場景的有兩個(gè)文本域的關(guān)系布局。
- 第二個(gè)場景的擁有與第一個(gè)布局相同內(nèi)容但內(nèi)容不同順序的關(guān)系布局。

樣例設(shè)計(jì)來讓動(dòng)畫發(fā)生在活動(dòng)的主布局的子布局中。在主布局中的文本標(biāo)簽仍然是靜態(tài)的。

活動(dòng)的主布局定義如下:
res/layout/activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/master_layout"><TextViewandroid:id="@+id/title"...android:text="Title"/><FrameLayoutandroid:id="@+id/scene_root"><include layout="@layout/a_scene" /></FrameLayout> </LinearLayout>

布局文件的定義包含了一個(gè)文本域和一個(gè)場景根的子布局。第一個(gè)場景的布局文件被包含在了主布局文件中。此允許應(yīng)用程序?qū)⒋俗鳛橛脩艚缑娴囊徊糠謥碚故就瑫r(shí)也能夠?qū)⒋溯d入到場景中,因?yàn)榭蚣苤荒軐⒄麄€(gè)布局文件載入場景中。

第一個(gè)場景的布局文件定義如下:
res/layout/a_scene.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/scene_container"android:layout_width="match_parent"android:layout_height="match_parent" ><TextView android:id="@+id/text_view1android:text="Text Line 1" /><TextView android:id="@+id/text_view2android:text="Text Line 2" /> </RelativeLayout>

擁有與第一個(gè)場景布局文件相同的文本域(相同的ID)但放置順序不同的第二個(gè)場景的布局文件內(nèi)容如下:
res/layout/another_scene.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/scene_container"android:layout_width="match_parent"android:layout_height="match_parent" ><TextView android:id="@+id/text_view2android:text="Text Line 2" /><TextView android:id="@+id/text_view1android:text="Text Line 1" /> </RelativeLayout>

[2] 根據(jù)布局生成場景
為兩個(gè)關(guān)系布局創(chuàng)建定義之后,就可以為每個(gè)布局文件各獲取一個(gè)場景了。這能夠使得稍后在兩個(gè)用戶界面配置之間作變換。欲獲得場景,需要場景根的引用和布局資源ID。

以下代碼片段展示了如何獲取場景根的引用,并根據(jù)布局文件創(chuàng)建兩個(gè)場景對象:

Scene mAScene; Scene mAnotherScene;// Create the scene root for the scenes in this app mSceneRoot = (ViewGroup) findViewById(R.id.scene_root);// Create the scenes mAScene = Scene.getSceneForLayout(mSceneRoot, R.layout.a_scene, this); mAnotherScene =Scene.getSceneForLayout(mSceneRoot, R.layout.another_scene, this);

此時(shí),在應(yīng)用程序中已有基于視圖層的兩個(gè)場景對象。兩個(gè)場景都使用在res/layout/activity_main.xml中被FrameLayout元素定義的場景根。

(2) 在代碼中創(chuàng)建場景
也可以根據(jù)ViewGroup對象用代碼創(chuàng)建場景實(shí)例。當(dāng)用代碼直接修改視圖層或動(dòng)態(tài)創(chuàng)建視圖時(shí)可使用此項(xiàng)技術(shù)。

欲根據(jù)視圖層用代碼創(chuàng)建場景,用Scene(sceneRoot, viewHierarchy)構(gòu)造函數(shù)。當(dāng)已經(jīng)有相應(yīng)的布局文件之后,調(diào)用此構(gòu)造方法等效調(diào)用Scene.getSceneForLayout()方法。

以下代碼片段演示如何根據(jù)場景根元素和視圖層創(chuàng)建一個(gè)視圖實(shí)例:

Scene mScene;// Obtain the scene root element mSceneRoot = (ViewGroup) mSomeLayoutElement;// Obtain the view hierarchy to add as a child of // the scene root when this scene is entered mViewHierarchy = (ViewGroup) someOtherLayoutElement;// Create a scene mScene = new Scene(mSceneRoot, mViewHierarchy);

(3) 創(chuàng)建場景動(dòng)作
框架允許定義在場景進(jìn)入運(yùn)行或場景退出運(yùn)行時(shí)的自定義場景動(dòng)作。在許多情況下,自定義場景動(dòng)作都沒必要,因?yàn)榭蚣茉趫鼍爸g自動(dòng)的動(dòng)畫改變。

場景動(dòng)作在處理以下情況中顯得有用:
- 動(dòng)畫視圖不在相同的視圖層上。用進(jìn)入或退出場景動(dòng)作引起場景開始或結(jié)束以使用動(dòng)畫視圖。
- 變換框架不能自動(dòng)進(jìn)行動(dòng)畫視圖,如ListView對象。更多信息見Limitations。

欲提供自定義的場景動(dòng)作,以Runnable對象定義動(dòng)作并將動(dòng)作傳遞給Scene.setExitAction()或Scene.setEnterAction()方法。框架在開始場景即運(yùn)行變換動(dòng)畫之前調(diào)用setExitAction()方法,在結(jié)束場景即運(yùn)行變換動(dòng)畫之后調(diào)用setEnterAction()方法。

注:不要用場景動(dòng)作在開始視圖和結(jié)束視圖之間傳遞數(shù)據(jù)。更多信息見Defining Transition Lifecycle Callbacks。

2015.11.17

3.3 請求轉(zhuǎn)換

學(xué)習(xí)如何變換視圖層次的兩個(gè)場景。

在變換框架中,動(dòng)畫創(chuàng)建了一系列描繪在開始和結(jié)束場景中的視圖層的改變的幀。框架代表的動(dòng)畫作為變換對象,其中包含動(dòng)畫的信息。欲運(yùn)行動(dòng)畫,需提供要使用的變換以及結(jié)束場景給變換方式。

此節(jié)向您展示用內(nèi)建的變換在兩個(gè)場景動(dòng)畫即移動(dòng)、調(diào)整尺寸以及漸褪視圖。下一節(jié)將向您演示如何自定義變換。

(1) 創(chuàng)建變換
在上一節(jié)中,您學(xué)會了如何創(chuàng)建代表不同視圖層狀態(tài)的場景。一旦定義了欲改變的開始場景和結(jié)束場景,就再需要?jiǎng)?chuàng)建一個(gè)定義動(dòng)畫的變換對象。框架能夠在資源文件中指定內(nèi)建的變換并且能將此關(guān)聯(lián)到到代碼中或直接在代碼中定義一個(gè)內(nèi)建變換的實(shí)例。

[1] 根據(jù)資源文件創(chuàng)建變換實(shí)例
此項(xiàng)技術(shù)能夠在不修改活動(dòng)代碼的情況下就能夠修改變換定義。此項(xiàng)技術(shù)也能夠?qū)?fù)雜的變換定義跟應(yīng)用程序代碼獨(dú)立開來,如 Specify Multiple Transitions中所述。

欲在資源文件中指定內(nèi)建變換,跟隨以下步驟:
- 增加/res/transition/目錄到工程 中。
- 在剛所建的目錄中新建一個(gè)XML資源文件。
- 將內(nèi)建變換作為節(jié)點(diǎn)添加到XML文件中。

例如,以下資源文件指定了消退(Fade)變換:
res/transition/fade_transition.xml

<fade xmlns:android="http://schemas.android.com/apk/res/android" />

以下代碼片段演示如何將資源文件中的變換實(shí)例關(guān)聯(lián)到活動(dòng)的代碼中:

Transition mFadeTransition =TransitionInflater.from(this).inflateTransition(R.transition.fade_transition);

[2] 在代碼中創(chuàng)建變換實(shí)例
此項(xiàng)技術(shù)對于在代碼中修改用戶界面動(dòng)態(tài)創(chuàng)建變換對象非常有用,對于創(chuàng)建簡單的內(nèi)建變換實(shí)例不需要或只需要很少的參數(shù)。

欲創(chuàng)建內(nèi)建變換實(shí)例,調(diào)用Transition類子類的其中一個(gè)構(gòu)造函數(shù)即可。例如,以下代碼片段創(chuàng)建了一個(gè)消退(fade)變換的實(shí)例:

Transition mFadeTransition = new Fade();

(2) 請求變換
一般來說,變換應(yīng)用于改變不同的視圖層以響應(yīng)諸如用戶動(dòng)作這樣的事件。例如,一個(gè)搜索應(yīng)用程序:當(dāng)用戶在搜索條中輸入內(nèi)容并點(diǎn)擊搜索按鈕時(shí),當(dāng)應(yīng)用消退(fade)變換時(shí),應(yīng)用程序改變到顯示搜索結(jié)果的場景之上,在此場景中搜索條消失不見。

當(dāng)應(yīng)用變換來響應(yīng)活動(dòng)中的某些事件來實(shí)現(xiàn)場景變換,需要用結(jié)束場景和變換實(shí)例來調(diào)用TransitionManager.go()靜態(tài)方法來實(shí)現(xiàn)動(dòng)畫,代碼如下所示:

TransitionManager.go(mEndingScene, mFadeTransition);

在根據(jù)指定變換實(shí)例運(yùn)行動(dòng)畫時(shí),框架根據(jù)結(jié)束場景的視圖層改變場景根下的視圖層。開始場景為上一次變換的結(jié)束場景。如果之前無任何變換,系統(tǒng)根據(jù)用戶界面當(dāng)前狀態(tài)作為開始場景。

如果沒有指定變換實(shí)例,變換管理器能夠?qū)⒆詣?dòng)應(yīng)用一個(gè)能夠響應(yīng)大多數(shù)情形的變換。更多信息見API參考TransitionManager類。

(3) 選擇特定的目標(biāo)視圖
框架應(yīng)用變換到默認(rèn)的開始場景和結(jié)束場景中的所有視圖。在某些清醒下,可能只想將變換應(yīng)用到場景中的某部分子視圖上。例如,框架不支持ListView對象的動(dòng)畫改變,所以在變換期間不會動(dòng)畫ListView對象。框架能夠只選擇欲動(dòng)畫的部分視圖。

將每個(gè)會進(jìn)行變換的視圖稱作目標(biāo)。只能將場景視圖層中的某些視圖作為目標(biāo)。

欲從目標(biāo)列表中移除一個(gè)或多個(gè)視圖,在開始變換前調(diào)用removeTarget()方法。欲增加視圖到目標(biāo)列表中,調(diào)用addTart()方法。更多信息見API 參考Transition類。

(4) 指定多個(gè)變換
欲獲得動(dòng)畫的最大效果,應(yīng)將動(dòng)畫跟場景間的變化匹配。例如,如果您正在場景間移除某些視圖的同時(shí)又在增添其它的視圖,消退(fade out)/消失(fade in)動(dòng)畫提供某些視圖不在可用的顯著提示。如果您正將視圖移到屏幕的不同點(diǎn)處,一個(gè)更好的選擇時(shí)動(dòng)畫移動(dòng)以讓用戶注意視圖的新位置。

不必只選擇一個(gè)動(dòng)畫,因?yàn)樽儞Q框架能夠在包含內(nèi)建或自定義變換組的變換集中結(jié)合動(dòng)畫效果。

欲根據(jù)XML變換集定義變換集,在res/transitions/目錄下創(chuàng)建資源文件并在transitionsSet元素下列出變換。以下代碼片段定義了跟AutoTransition類相同行為的變換集:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"android:transitionOrdering="sequential"><fade android:fadingMode="fade_out" /><changeBounds /><fade android:fadingMode="fade_in" /> </transitionSet>

欲在代碼中將此變換集設(shè)置到TransitionSet對象中,在活動(dòng)中調(diào)用 TransitionInflater.from()方法。TransitionSet類從Transition類繼承,所以可以像其它變換實(shí)例一樣用變換管理器來使用它。

(5) 請求無場景變換
改變視圖層不是修改用戶界面的唯一方式。亦可以在當(dāng)前層次中通過增加、修改以及移除子視圖的方式改變用戶界面。例如,可以將搜索條實(shí)現(xiàn)在一個(gè)單獨(dú)的布局文件中。開始顯示搜索條。欲改變用戶界面來顯示搜索結(jié)果,當(dāng)用戶點(diǎn)擊搜索條時(shí)調(diào)用ViewGroup.removeView()方法來將搜索條移除,并通過調(diào)用ViewGroup.addView()方法將搜索結(jié)果增加到用戶界面中。

如果會相互替代的兩層的內(nèi)容幾乎相同可用此方法來實(shí)現(xiàn)用戶界面的改變。否則,還是創(chuàng)建動(dòng)畫來實(shí)現(xiàn)用戶界面的改變,這樣就可以只用包含可在代碼中修改的視圖層的一個(gè)布局文件。

如果以上述方法來改變當(dāng)前視圖層,就不再需要?jiǎng)?chuàng)建場景。可以創(chuàng)建并應(yīng)用延遲變換到視圖層的兩個(gè)狀態(tài)中。框架的這個(gè)特點(diǎn)開始于當(dāng)前視圖狀態(tài),記錄對視圖作的改變,當(dāng)系統(tǒng)重繪用戶界面是變換將動(dòng)畫改變。

欲在單個(gè)視圖中創(chuàng)建延遲變換,需以下步驟:
[1]. 當(dāng)觸發(fā)變換的事件發(fā)生時(shí),調(diào)用方法來提供欲用變換來改變的所有子視圖的父視圖。框架存儲子視圖當(dāng)前的狀態(tài)和特性值。
[2]. 欲改變子視圖需要用戶使用子視圖。框架記錄用戶對子視圖所作的改變及其特性。
[3]. 當(dāng)系統(tǒng)根據(jù)改變重繪用戶界面時(shí),框架從原始狀態(tài)動(dòng)畫改變到新狀態(tài)。

以下代碼樣例展示如何使用延遲變換將一個(gè)文本視圖動(dòng)畫的添加到視圖層中。第一段代碼片段展示的布局文件中的定義:
res/layout/activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/mainLayout"android:layout_width="match_parent"android:layout_height="match_parent" ><EditTextandroid:id="@+id/inputText"android:layout_alignParentLeft="true"android:layout_alignParentTop="true"android:layout_width="match_parent"android:layout_height="wrap_content" />... </RelativeLayout>

第二個(gè)代碼片段展示動(dòng)畫增加文本視圖的過程:
MainActivity.java

private TextView mLabelText; private Fade mFade; private ViewGroup mRootView; ...// Load the layout this.setContentView(R.layout.activity_main); ...// Create a new TextView and set some View properties mLabelText = new TextView(); mLabelText.setText("Label").setId("1");// Get the root view and create a transition mRootView = (ViewGroup) findViewById(R.id.mainLayout); mFade = new Fade(IN);// Start recording changes to the view hierarchy TransitionManager.beginDelayedTransition(mRootView, mFade);// Add the new TextView to the view hierarchy mRootView.addView(mLabelText);// When the system redraws the screen to show this update, // the framework will animate the addition as a fade in

(6) 定義變換聲明周期回調(diào)方法
變換的生命周期類似活動(dòng)的生命周期。它代表框架在調(diào)用TransitionManager.go()方法和完成動(dòng)畫期間所監(jiān)控的變換的狀態(tài)。在重要的生命周期狀態(tài)中,框架調(diào)用被TransitionListener接口定義的回調(diào)方法。

變換的生命周期回調(diào)函數(shù)很有用,例如,在場景改變時(shí)復(fù)制從開始場景到結(jié)束場景某個(gè)視圖的特性值。不可簡單的在視圖層中復(fù)制開始視圖和結(jié)束視圖,因?yàn)榻Y(jié)束視圖層在變換完成前沒有被關(guān)聯(lián)。科學(xué)的做法是,將值保存在某變量中在框架完成變換后再將此變量拷貝到結(jié)束場景中。欲獲得變換結(jié)束的通知,在活動(dòng)中實(shí)現(xiàn)TransitionListener.onTransitionEnd()方法。

更多信息見API參考TransitionListener類。

3.4 創(chuàng)建自定義變換

學(xué)習(xí)如何創(chuàng)建不屬于變換框架中的其它的動(dòng)畫效果。

自定義變換創(chuàng)建的動(dòng)畫對于任何內(nèi)建變換類都不可用。例如,可以定義一個(gè)變換來返回文本的前景色并將輸入域設(shè)置為灰色以按時(shí)此域在屏幕上已經(jīng)失去了輸入功能。這中效果將幫助用戶理解此域失去了輸入功能。

就像內(nèi)建變換類型一樣,自定義的變換可以應(yīng)用動(dòng)畫到開始和結(jié)束場景中的子視圖中。然而,也不像內(nèi)建變換類型,需要提供來獲取特性值和產(chǎn)生動(dòng)畫的代碼。也可以圍動(dòng)畫定義視圖目標(biāo)的子集。

此節(jié)教您獲取特性值和產(chǎn)生動(dòng)畫來創(chuàng)建自定義變換。

(1) 擴(kuò)展變換類
欲創(chuàng)建自定義變換,增加擴(kuò)展Transition類的類到工程中并重寫方法,如以下代碼所示:

public class CustomTransition extends Transition {@Overridepublic void captureStartValues(TransitionValues values) {}@Overridepublic void captureEndValues(TransitionValues values) {}@Overridepublic Animator createAnimator(ViewGroup sceneRoot,TransitionValues startValues,TransitionValues endValues) {} }

后續(xù)內(nèi)容解釋如何重寫這些方法。

(2) 獲取視圖屬性值
變換動(dòng)畫用屬性動(dòng)畫中的屬性動(dòng)畫系統(tǒng)。屬性動(dòng)畫在開始和結(jié)束值中的一段特殊時(shí)間改變視圖屬性,所以框架需要屬性的開始和結(jié)束值來構(gòu)建動(dòng)畫。

然而,屬性動(dòng)畫通常只需要視圖屬性值的一個(gè)子集。例如,顏色動(dòng)畫需要顏色屬性值,移動(dòng)動(dòng)畫需要位置屬性值。由于動(dòng)畫所需的屬性值對于變化來說特殊,變化框架不會為變換提供每一個(gè)屬性值。取而代之的是,框架將調(diào)用可以為變換獲取變換所需的屬性值的回調(diào)方法并將屬性值存儲在框架中。

[1] 獲取開始值
欲傳遞開始視圖值給框架,需實(shí)現(xiàn)captureStartValues(transitionValues)方法。框架將調(diào)用此方法來獲取開始場景中的每一視圖。此方法的參數(shù)是一個(gè)Transitionvalues對象,器包含一個(gè)視圖引用和一個(gè)能夠存儲視圖值的Map實(shí)例。在獲取開始值的實(shí)現(xiàn)中,檢索這些屬性值將并將存儲在Map中的屬性值回傳給框架。

欲確定屬性值的鍵值不會和其它的TransitionValues鍵值沖突,用以下的命名方案:
package_name:transition_name:property_name

以下代碼片段展示了captureStarValues()方法的實(shí)現(xiàn):

public class CustomTransition extends Transition {// Define a key for storing a property value in// TransitionValues.values with the syntax// package_name:transition_class:property_name to avoid collisionsprivate static final String PROPNAME_BACKGROUND ="com.example.android.customtransition:CustomTransition:background";@Overridepublic void captureStartValues(TransitionValues transitionValues) {// Call the convenience method captureValuescaptureValues(transitionValues);}// For the view in transitionValues.view, get the values you// want and put them in transitionValues.valuesprivate void captureValues(TransitionValues transitionValues) {// Get a reference to the viewView view = transitionValues.view;// Store its background property in the values maptransitionValues.values.put(PROPNAME_BACKGROUND, view.getBackground());}... }

[2] 獲取結(jié)束值
框架在結(jié)束場景中為每個(gè)目標(biāo)視圖調(diào)用一次captureEndValues(TransitionValues)方法。在其它方面,captureEndValues()跟captureStartValues()工作機(jī)制一致。

以下代碼片段顯示了captureEndValues()方法的實(shí)現(xiàn):

@Override public void captureEndValues(TransitionValues transitionValues) {captureValues(transitionValues); }

在此例中,captureStartValues()和 captureEndValues()方法都調(diào)用了captureValues()方法來檢索和保存值。captureValues()方法檢索到的視圖屬性相同,但它在開始和結(jié)束場景中有著不同的值。框架分開映射開始和結(jié)束場景視圖的值。

(3) 創(chuàng)建自定義動(dòng)畫
欲動(dòng)畫視圖在其開始和結(jié)束場景中的改變,需要重寫createAnimator()方法來提供動(dòng)畫器。當(dāng)框架調(diào)用此方法時(shí),它將傳遞動(dòng)畫器給場景根視圖和所捕獲的包含開始和結(jié)束場景給TransitionValues對象。

框架調(diào)用createAnimator()方法的次數(shù)基于開始和結(jié)束場景的改變。例如,自定義實(shí)現(xiàn)的消退/消失變換。如在開始場景中景中有5個(gè)目標(biāo)但在結(jié)束場景中會被移除兩個(gè),那么在結(jié)束場景就只有三個(gè)目標(biāo),再在結(jié)束場景中添加一個(gè)新目標(biāo)。那么框架將會調(diào)用createAnimator()方法六次。三次調(diào)用用于兩個(gè)場景中的消退/消失;兩次調(diào)用為移除的目標(biāo)動(dòng)畫;一次調(diào)用為結(jié)束場景中的新目標(biāo)。

對于存在于開始和結(jié)束場景中的視圖目標(biāo),框架為startValues和endValues參數(shù)提供了Transitions對象。對于只存在于開始或結(jié)束場景中的目標(biāo)視圖,框架提供TransitionValues對象來聯(lián)系參數(shù)和并用null聯(lián)系其它。

當(dāng)創(chuàng)建自定義變換實(shí)現(xiàn)createAnimator(ViewGroup, TransitionValues, TransitionValues)方法時(shí),用捕獲到的視圖屬性值來創(chuàng)建Animator對象并將此返回給框架。一個(gè)實(shí)現(xiàn)的樣例,見自定義變換樣例中的ChangeColor類。更多關(guān)于屬性動(dòng)畫的值 Property Animation見。

(4) 應(yīng)用自定義變換
自定義變換的工作機(jī)制跟內(nèi)建變換相同。可以用變換管理應(yīng)用于自定義變換,具體描述見 Applying a Transition。

4. 增加動(dòng)畫

如何將漸變的動(dòng)畫添加到用戶界面中。

動(dòng)畫能夠?yàn)橥ㄖ脩絷P(guān)于應(yīng)用程序發(fā)生了啥增加微妙的視覺線索并且會增加應(yīng)用程序界面的心智模型(mental model)。當(dāng)屏幕改變狀態(tài)時(shí)動(dòng)畫尤其有用,如當(dāng)內(nèi)容載入或新動(dòng)作變得有用時(shí)。同時(shí)動(dòng)畫也能夠?yàn)閼?yīng)用程序增加光滿的外觀,這可以給應(yīng)用程序一個(gè)更高質(zhì)量的感覺。

但仍需記住,過度的使用動(dòng)畫或在錯(cuò)誤的時(shí)間使用動(dòng)畫也會帶來不利,如引起延遲。此節(jié)展示如何實(shí)現(xiàn)一些常見的能夠帶來實(shí)用并在不騷擾用戶的情況下增加流動(dòng)性的動(dòng)畫類型。

2015.11.18

4.1 兩視圖交叉淡入淡出

學(xué)習(xí)如何讓兩個(gè)重疊的視圖淡入淡出。此節(jié)展示如何讓進(jìn)度條淡入包含文本內(nèi)容的視圖。

淡入淡出動(dòng)畫(亦稱溶解)漸漸的消退某用戶界面組件的同時(shí)漸入另一個(gè)組件。此動(dòng)畫對于轉(zhuǎn)換內(nèi)容或視圖到應(yīng)用程序的情形很有用。淡入淡出非常微妙同時(shí)也很短暫但提供了屏幕到文本的流利的變換。當(dāng)不用淡入淡出動(dòng)畫時(shí),這些變換都會顯得有些突然。

此處有一個(gè)從進(jìn)度條淡出文本內(nèi)容的例子:http://developer.android.com/training/animation/anim_crossfade.mp4
淡入淡出動(dòng)畫
點(diǎn)擊屏幕設(shè)備屏幕可放映動(dòng)畫

如果您想跳過后續(xù)內(nèi)容并想看一個(gè)完整的代碼示例,下載并運(yùn)行樣例,選擇淡入淡出的例子。看一下幾個(gè)文件中的代碼實(shí)現(xiàn):
· src/CrossfadeActivity.java
· layout/activity_crossfade.xml
· menu/activity_crossfade.xml

(1) 創(chuàng)建視圖
創(chuàng)建欲淡入淡出的兩個(gè)視圖。以下代碼片段創(chuàng)建了一個(gè)進(jìn)度條和一個(gè)具滑動(dòng)條的文本視圖:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><ScrollView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/content"android:layout_width="match_parent"android:layout_height="match_parent"><TextView style="?android:textAppearanceMedium"android:lineSpacingMultiplier="1.2"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@string/lorem_ipsum"android:padding="16dp" /></ScrollView><ProgressBar android:id="@+id/loading_spinner"style="?android:progressBarStyleLarge"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center" /></FrameLayout>

(2) 設(shè)置動(dòng)畫
欲設(shè)置動(dòng)畫,遵循以下步驟:
[1] 為欲淡入淡出的視圖創(chuàng)建成員變量。在動(dòng)畫期間需要這些視圖的引用來修改視圖。
[2] 對于淡入的視圖,將其可見性設(shè)置為GONE。此值能夠避免視圖占據(jù)布局文件空間并忽略堆它們的計(jì)算以提高處理速度。
[3] 將config_shortAnimTime系統(tǒng)屬性緩存在成員變量中。此屬性為動(dòng)畫定義了一個(gè)標(biāo)準(zhǔn)的“短”的持續(xù)時(shí)間。此持續(xù)時(shí)間對微妙的動(dòng)畫或發(fā)生頻率較高的動(dòng)畫比較理想。config_longAnimTime和config_mediumAnimTime也是可用的,如果您想用它們的話。

將前面代碼片段所定義的內(nèi)容作為以下代碼描述的活動(dòng)的布局文件:

public class CrossfadeActivity extends Activity {private View mContentView;private View mLoadingView;private int mShortAnimationDuration;...@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_crossfade);mContentView = findViewById(R.id.content);mLoadingView = findViewById(R.id.loading_spinner);// Initially hide the content view.mContentView.setVisibility(View.GONE);// Retrieve and cache the system's default "short" animation time.mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);}

(3) 淡入淡出視圖
至此,視圖已被正確設(shè)置,欲對這些視圖進(jìn)行淡入淡出請遵循以下步驟:
[1] 對于淡入的視圖,設(shè)置alpha值為0并將其可見值設(shè)置為VISIBLE。(記住其初始值為GONE)這樣能夠讓這些視圖可見但出于完全透明的狀態(tài)。
[2] 對于淡入的視圖,動(dòng)畫改變其alpha的值從0到1。同時(shí),將淡出視圖的alpha值從1動(dòng)畫改為0。
[3] 在Animator.AnimatorListener中使用onAnimationEnd()方法,將淡出視圖的可見性設(shè)置為GONE。盡管這些視圖的alpha值為0,將視圖的可見性設(shè)置為GONE能夠阻止視圖占用布局文件空間且忽略布局計(jì)算,以提升處理速度。

以下方法展示如何做以上描述的步驟:

private View mContentView; private View mLoadingView; private int mShortAnimationDuration;...private void crossfade() {// Set the content view to 0% opacity but visible, so that it is visible// (but fully transparent) during the animation.mContentView.setAlpha(0f);mContentView.setVisibility(View.VISIBLE);// Animate the content view to 100% opacity, and clear any animation// listener set on the view.mContentView.animate().alpha(1f).setDuration(mShortAnimationDuration).setListener(null);// Animate the loading view to 0% opacity. After the animation ends,// set its visibility to GONE as an optimization step (it won't// participate in layout passes, etc.)mLoadingView.animate().alpha(0f).setDuration(mShortAnimationDuration).setListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {mLoadingView.setVisibility(View.GONE);}}); }

4.2 使用ViewPager屏幕滑動(dòng)

學(xué)習(xí)在滑動(dòng)變換下動(dòng)畫變化屏幕。

屏幕滑動(dòng)是從一個(gè)屏幕頁面到另一個(gè)屏幕頁面的變換。此節(jié)內(nèi)容將展示如何用由support library提供的ViewPager來做到屏幕滑動(dòng)變換。ViewPager自動(dòng)掃描屏幕滑動(dòng)。點(diǎn)擊下圖屏幕,由本頁屏幕會自動(dòng)滑動(dòng)到下一頁屏幕:

屏幕滑動(dòng)動(dòng)畫:http://developer.android.com/training/animation/anim_screenslide.mp4
屏幕滑動(dòng)動(dòng)畫
點(diǎn)擊屏幕設(shè)備屏幕可放映動(dòng)畫

如果您想跳過后續(xù)內(nèi)容且想看完整的工程代碼,下載本節(jié)樣例應(yīng)用程序,選擇屏幕滑動(dòng)(Screen Slide)例子。看以下幾個(gè)文件中的代碼實(shí)現(xiàn):
- src/ScreenSlidePageFragment.java
- src/ScreenSlideActivity.java
- layout/activity_screen_slide.xml
- layout/fragment_screen_slide_page.xml

(1) 創(chuàng)建視圖
為稍后要使用的碎片的內(nèi)容創(chuàng)建一個(gè)布局文件。以下實(shí)現(xiàn)的布局文件中包含一個(gè)顯示文本的文本視圖:

<!-- fragment_screen_slide_page.xml --> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/content"android:layout_width="match_parent"android:layout_height="match_parent" ><TextView style="?android:textAppearanceMedium"android:padding="16dp"android:lineSpacingMultiplier="1.2"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@string/lorem_ipsum" /> </ScrollView>

同時(shí)也在碎片中定義了一個(gè)字符串。

(2) 創(chuàng)建碎片
創(chuàng)建一個(gè)返回在onCreateView()方法中創(chuàng)建的布局的Fragment(碎片)類。然后,在需要向用戶展示新頁的時(shí)候就可以在碎片的父活動(dòng)中創(chuàng)建此片段實(shí)例:

import android.support.v4.app.Fragment; ... public class ScreenSlidePageFragment extends Fragment {@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_screen_slide_page, container, false);return rootView;} }

(3) 增加ViewPager
ViewPager已經(jīng)被內(nèi)建在掃頁面變換中,它的默認(rèn)功能就是滑動(dòng)屏幕動(dòng)畫,所以不必重新創(chuàng)建它。ViewPager展示PagerAdapter提供的頁面,所以PagerAdapter也會用到之前所創(chuàng)建的碎片。

首先,在布局文件 中包含ViewPager:

<!-- activity_screen_slide.xml --> <android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/pager"android:layout_width="match_parent"android:layout_height="match_parent" />

創(chuàng)建一個(gè)具以下功能的活動(dòng):
- 將活動(dòng)的視圖內(nèi)容設(shè)置包含ViewPager的布局文件。
- 創(chuàng)建一個(gè)擴(kuò)展于FragmentStatePagerAdapter類的類并實(shí)現(xiàn)getItem()方法來提供一個(gè)ScreenSlidePageFragment實(shí)例作為新頁。頁面適配器同樣要求實(shí)現(xiàn)getCount()方法,此方法返回適配器所需創(chuàng)建的頁面數(shù)。
- 將PagerAdapter掛到ViewPager上。
- 在碎片的虛擬棧中通過回移來響應(yīng)設(shè)備的返回按鈕。如果用戶已經(jīng)在第一頁上,就返回到活動(dòng)的棧底。

import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; ... public class ScreenSlidePagerActivity extends FragmentActivity {/*** The number of pages (wizard steps) to show in this demo.*/private static final int NUM_PAGES = 5;/*** The pager widget, which handles animation and allows swiping horizontally to access previous* and next wizard steps.*/private ViewPager mPager;/*** The pager adapter, which provides the pages to the view pager widget.*/private PagerAdapter mPagerAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_screen_slide);// Instantiate a ViewPager and a PagerAdapter.mPager = (ViewPager) findViewById(R.id.pager);mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());mPager.setAdapter(mPagerAdapter);}@Overridepublic void onBackPressed() {if (mPager.getCurrentItem() == 0) {// If the user is currently looking at the first step, allow the system to handle the// Back button. This calls finish() on this activity and pops the back stack.super.onBackPressed();} else {// Otherwise, select the previous step.mPager.setCurrentItem(mPager.getCurrentItem() - 1);}}/*** A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in* sequence.*/private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {public ScreenSlidePagerAdapter(FragmentManager fm) {super(fm);}@Overridepublic Fragment getItem(int position) {return new ScreenSlidePageFragment();}@Overridepublic int getCount() {return NUM_PAGES;}} }

(4)用PageTransformer自定義動(dòng)畫
欲在默認(rèn)的屏幕滑動(dòng)動(dòng)畫中展示不同的動(dòng)畫頁面,實(shí)現(xiàn)ViewPager.PagerTransformer接口并將此接口提供給視圖頁。此接口指暴露了transformPage()一個(gè)方法。每當(dāng)屏幕變換時(shí),每個(gè)可見視圖(通常屏幕上只有一個(gè)可見頁面)以及相鄰的沒有在屏幕上的頁面就會調(diào)用此方法一次。例如,若當(dāng)前屏幕為頁面三,用戶欲拖拽出頁面四,在每一個(gè)手勢發(fā)生時(shí),transformPage()會被頁面二、三、四調(diào)用。

在transformPage()的實(shí)現(xiàn)中,根據(jù)屏幕上頁面的位置參數(shù)通過判斷哪一個(gè)頁面需要轉(zhuǎn)換可以創(chuàng)建自定義的動(dòng)畫滑動(dòng),位置可從ransformPage()方法的position參數(shù)獲得。

位置position參數(shù)會表明一個(gè)頁面跟屏幕中心的位置關(guān)系。當(dāng)用戶滑動(dòng)屏幕時(shí)此參數(shù)是一個(gè)動(dòng)態(tài)值。當(dāng)某頁面填充到屏幕中時(shí),其位置參數(shù)值為0。當(dāng)一個(gè)頁面剛好從屏幕右邊消失時(shí),其位置值為1。如果用戶在頁面1和頁面2中滑動(dòng)一半,頁面1的位置值為-0.5,頁面2的位置值為0.5。基于頁面在屏幕上的具體位置,可以通過setAlpha()、setTranslationX()或setScaleY()方法設(shè)置頁面屬性值來自定義動(dòng)畫。

當(dāng)實(shí)現(xiàn)PagerTransformer后,用此實(shí)現(xiàn)調(diào)用setPageTransformer()來應(yīng)用自定義的動(dòng)畫。例如,假設(shè)有一個(gè)名為ZoomOutPageTransformer的PagerTransformer,可以像以下這樣設(shè)置自定義動(dòng)畫:

ViewPager mPager = (ViewPager) findViewById(R.id.pager); ... mPager.setPageTransformer(true, new ZoomOutPageTransformer());

見Zoom-out page transformer和Depth page transformer部分的例子及相應(yīng)變換的視頻。

[1] 頁面縮小變換
當(dāng)用戶在相鄰頁面滑動(dòng)時(shí),頁面將會縮小并消退出屏幕。當(dāng)頁面接近屏幕中心時(shí),此頁面將回到正常尺寸并漸入。

頁面縮小變換動(dòng)畫:http://developer.android.com/training/animation/anim_page_transformer_zoomout.mp4
ZoomOutPageTransformer示例
點(diǎn)擊設(shè)備屏幕放映動(dòng)畫

public class ZoomOutPageTransformer implements ViewPager.PageTransformer {private static final float MIN_SCALE = 0.85f;private static final float MIN_ALPHA = 0.5f;public void transformPage(View view, float position) {int pageWidth = view.getWidth();int pageHeight = view.getHeight();if (position < -1) { // [-Infinity,-1)// This page is way off-screen to the left.view.setAlpha(0);} else if (position <= 1) { // [-1,1]// Modify the default slide transition to shrink the page as wellfloat scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));float vertMargin = pageHeight * (1 - scaleFactor) / 2;float horzMargin = pageWidth * (1 - scaleFactor) / 2;if (position < 0) {view.setTranslationX(horzMargin - vertMargin / 2);} else {view.setTranslationX(-horzMargin + vertMargin / 2);}// Scale the page down (between MIN_SCALE and 1)view.setScaleX(scaleFactor);view.setScaleY(scaleFactor);// Fade the page relative to its size.view.setAlpha(MIN_ALPHA +(scaleFactor - MIN_SCALE) /(1 - MIN_SCALE) * (1 - MIN_ALPHA));} else { // (1,+Infinity]// This page is way off-screen to the right.view.setAlpha(0);}} }

[2] 頁面深度變換
當(dāng)用“depth”動(dòng)畫滑動(dòng)頁面到右邊時(shí),頁面用默認(rèn)的動(dòng)畫變換將滑動(dòng)頁面移到左邊。深度變換將頁面淡出并線性的減小其范圍。

頁面深度變換示例:http://developer.android.com/training/animation/anim_page_transformer_depth.mp4
DepthPageTransformer 示例
點(diǎn)擊設(shè)備屏幕放映動(dòng)畫

注:在深度動(dòng)畫期間,默認(rèn)的動(dòng)畫(屏幕滑動(dòng))仍舊發(fā)生了,所以必須構(gòu)建一個(gè)X負(fù)方向的變換。例如:

view.setTranslationX(-1 * view.getWidth() * position);

以下代碼演示如何在正移動(dòng)的頁面變換中抵消默認(rèn)的屏幕動(dòng)畫滑動(dòng):

public class DepthPageTransformer implements ViewPager.PageTransformer {private static final float MIN_SCALE = 0.75f;public void transformPage(View view, float position) {int pageWidth = view.getWidth();if (position < -1) { // [-Infinity,-1)// This page is way off-screen to the left.view.setAlpha(0);} else if (position <= 0) { // [-1,0]// Use the default slide transition when moving to the left pageview.setAlpha(1);view.setTranslationX(0);view.setScaleX(1);view.setScaleY(1);} else if (position <= 1) { // (0,1]// Fade the page out.view.setAlpha(1 - position);// Counteract the default slide transitionview.setTranslationX(pageWidth * -position);// Scale the page down (between MIN_SCALE and 1)float scaleFactor = MIN_SCALE+ (1 - MIN_SCALE) * (1 - Math.abs(position));view.setScaleX(scaleFactor);view.setScaleY(scaleFactor);} else { // (1,+Infinity]// This page is way off-screen to the right.view.setAlpha(0);}} }

4.3 顯示卡片翻轉(zhuǎn)式動(dòng)畫

學(xué)習(xí)在翻轉(zhuǎn)運(yùn)動(dòng)下如何實(shí)現(xiàn)兩視圖之間的動(dòng)畫。

此節(jié)將展示如何用自定義碎片動(dòng)畫來實(shí)現(xiàn)卡片翻轉(zhuǎn)動(dòng)畫。視圖內(nèi)容間的卡片翻轉(zhuǎn)通過模擬卡片翻轉(zhuǎn)過來實(shí)現(xiàn)。

卡片翻轉(zhuǎn)的過程如下所示:http://developer.android.com/training/animation/anim_card_flip.mp4
卡片翻轉(zhuǎn)動(dòng)畫,點(diǎn)擊設(shè)備屏幕放映動(dòng)畫

欲跳過后續(xù)內(nèi)容而想看完整的樣例,下載本節(jié)樣例選擇Card Flip樣例打開看以下幾個(gè)文件中的代碼實(shí)現(xiàn):
- src/CardFlipActivity.java
- animator/card_flip_right_in.xml
- animator/card_flip_right_out.xml
- animator/card_flip_left_in.xml
- animator/card_flip_left_out.xml
- layout/fragment_card_back.xml
- layout/fragment_card_front.xml

(1) 創(chuàng)建動(dòng)畫
欲創(chuàng)建卡片式動(dòng)畫翻轉(zhuǎn),需要兩個(gè)動(dòng)畫場景,當(dāng)前面的卡片動(dòng)畫從向左翻轉(zhuǎn)出去時(shí)另外一個(gè)動(dòng)畫要從左方顯示進(jìn)來。同時(shí),當(dāng)卡片動(dòng)畫從右方向回來時(shí)另一動(dòng)畫需要從右方向消失。

card_flip_left_in.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"><!-- Before rotating, immediately set the alpha to 0. --><objectAnimator android:valueFrom="1.0"android:valueTo="0.0"android:propertyName="alpha"android:duration="0" /><!-- Rotate. --><objectAnimator android:valueFrom="-180"android:valueTo="0"android:propertyName="rotationY"android:interpolator="@android:interpolator/accelerate_decelerate"android:duration="@integer/card_flip_time_full" /><!-- Half-way through the rotation (see startOffset), set the alpha to 1. --><objectAnimator android:valueFrom="0.0"android:valueTo="1.0"android:propertyName="alpha"android:startOffset="@integer/card_flip_time_half"android:duration="1" /> </set>

card_flip_left_out.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"><!-- Rotate. --><objectAnimator android:valueFrom="0"android:valueTo="180"android:propertyName="rotationY"android:interpolator="@android:interpolator/accelerate_decelerate"android:duration="@integer/card_flip_time_full" /><!-- Half-way through the rotation (see startOffset), set the alpha to 0. --><objectAnimator android:valueFrom="1.0"android:valueTo="0.0"android:propertyName="alpha"android:startOffset="@integer/card_flip_time_half"android:duration="1" /> </set>

card_flip_right_in.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"><!-- Before rotating, immediately set the alpha to 0. --><objectAnimator android:valueFrom="1.0"android:valueTo="0.0"android:propertyName="alpha"android:duration="0" /><!-- Rotate. --><objectAnimator android:valueFrom="180"android:valueTo="0"android:propertyName="rotationY"android:interpolator="@android:interpolator/accelerate_decelerate"android:duration="@integer/card_flip_time_full" /><!-- Half-way through the rotation (see startOffset), set the alpha to 1. --><objectAnimator android:valueFrom="0.0"android:valueTo="1.0"android:propertyName="alpha"android:startOffset="@integer/card_flip_time_half"android:duration="1" />

card_flip_right_out.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"><!-- Rotate. --><objectAnimator android:valueFrom="0"android:valueTo="-180"android:propertyName="rotationY"android:interpolator="@android:interpolator/accelerate_decelerate"android:duration="@integer/card_flip_time_full" /><!-- Half-way through the rotation (see startOffset), set the alpha to 0. --><objectAnimator android:valueFrom="1.0"android:valueTo="0.0"android:propertyName="alpha"android:startOffset="@integer/card_flip_time_half"android:duration="1" /> </set>

(2) 創(chuàng)建視圖
“卡片”的每一面可以獨(dú)立包含任何內(nèi)容,如都包含文本、圖片以及有關(guān)聯(lián)的視圖。動(dòng)畫變換時(shí)將會用存儲在碎片中的視圖布局。以下布局創(chuàng)建了卡片用用于顯示文本的一面:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="#a6c"android:padding="16dp"android:gravity="bottom"><TextView android:id="@android:id/text1"style="?android:textAppearanceLarge"android:textStyle="bold"android:textColor="#fff"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@string/card_back_title" /><TextView style="?android:textAppearanceSmall"android:textAllCaps="true"android:textColor="#80ffffff"android:textStyle="bold"android:lineSpacingMultiplier="1.2"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@string/card_back_description" /></LinearLayout>

卡片的另一面用于展示ImageView:

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:src="@drawable/image1"android:scaleType="centerCrop"android:contentDescription="@string/description_image_1" />

(3) 創(chuàng)建碎片
為卡片的前面和背面創(chuàng)建碎片類。此類返回之前在每個(gè)片段中onCreateView()方法中所創(chuàng)建的布局。之后,可在欲顯示卡片的碎片的父活動(dòng)中聲明此碎片實(shí)例。以下代碼展示了在父活動(dòng)中嵌套碎片類的實(shí)現(xiàn):

public class CardFlipActivity extends Activity {.../*** A fragment representing the front of the card.*/public class CardFrontFragment extends Fragment {@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_card_front, container, false);}}/*** A fragment representing the back of the card.*/public class CardBackFragment extends Fragment {@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_card_back, container, false);}} }

(4) 動(dòng)畫卡片翻轉(zhuǎn)
至此,可以在父活動(dòng)中展示片段了。欲此,首先為活動(dòng)創(chuàng)建布局文件。以下代碼創(chuàng)建了可以在運(yùn)行時(shí)添加碎片的包含F(xiàn)rameLayout元素的布局文件:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent" />

在活動(dòng)類代碼中,將剛創(chuàng)建的布局文件加載到活動(dòng)類中。在活動(dòng)被創(chuàng)建時(shí)顯示默認(rèn)的片段也是個(gè)不錯(cuò)的主意,所以以下活動(dòng)類中的代碼展示如何將卡片前面作為默認(rèn)顯示:

public class CardFlipActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_activity_card_flip);if (savedInstanceState == null) {getFragmentManager().beginTransaction().add(R.id.container, new CardFrontFragment()).commit();}}... }

現(xiàn)在以后卡片的前面的顯示,在適當(dāng)?shù)臅r(shí)候可以用翻轉(zhuǎn)動(dòng)畫來顯示卡片的背面。遵循以下步驟來顯示卡片的另一面:
- 設(shè)置之前為碎片變換創(chuàng)建的自定義動(dòng)畫。
- 用新的碎片代替當(dāng)前碎片的顯示并觸發(fā)自定義動(dòng)畫。
- 將之前的碎片置于棧頂?shù)南乱粚?#xff0c;這樣只要用戶按下返回按鈕,卡片就翻轉(zhuǎn)過來了。

private void flipCard() {if (mShowingBack) {getFragmentManager().popBackStack();return;}// Flip to the back.mShowingBack = true;// Create and commit a new fragment transaction that adds the fragment for the back of// the card, uses custom animations, and is part of the fragment manager's back stack.getFragmentManager().beginTransaction()// Replace the default fragment animations with animator resources representing// rotations when switching to the back of the card, as well as animator// resources representing rotations when flipping back to the front (e.g. when// the system Back button is pressed)..setCustomAnimations(R.animator.card_flip_right_in, R.animator.card_flip_right_out,R.animator.card_flip_left_in, R.animator.card_flip_left_out)// Replace any fragments currently in the container view with a fragment// representing the next page (indicated by the just-incremented currentPage// variable)..replace(R.id.container, new CardBackFragment())// Add this transaction to the back stack, allowing users to press Back// to get to the front of the card..addToBackStack(null)// Commit the transaction..commit(); }

4.4 縮放視圖

學(xué)習(xí)在縮放觸摸動(dòng)畫操作下如何放大視圖。

此節(jié)演示如何實(shí)現(xiàn)觸摸-縮放動(dòng)畫,此動(dòng)畫對于像相片畫廊引用程序非常有用 - 從縮略圖視圖動(dòng)畫到全尺寸以填充屏幕。

這里有一個(gè)觸摸-縮放動(dòng)畫:http://developer.android.com/training/animation/anim_zoom.mp4
縮放動(dòng)畫,點(diǎn)擊屏幕放映動(dòng)畫

如果想直接看本節(jié)代碼示例,下載并選擇Zoom樣例,見以下幾個(gè)文件中的代碼實(shí)現(xiàn):
- src/TouchHighlightImageButton.java(一個(gè)幫助類,當(dāng)按圖片按鈕時(shí)高亮點(diǎn)擊的地方)
- src/ZoomActivity.java
- layout/activity_zoom.xml

(1) 創(chuàng)建視圖
創(chuàng)建一個(gè)包含縮放所需的小和大尺寸視圖的布局文件。以下代碼創(chuàng)建了一個(gè)具點(diǎn)擊響應(yīng)事件的ImageButton按鈕以及一個(gè)展示擴(kuò)大圖片的的ImageView視圖:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayout android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:padding="16dp"><ImageButtonandroid:id="@+id/thumb_button_1"android:layout_width="100dp"android:layout_height="75dp"android:layout_marginRight="1dp"android:src="@drawable/thumb1"android:scaleType="centerCrop"android:contentDescription="@string/description_image_1" /></LinearLayout><!-- This initially-hidden ImageView will hold the expanded/zoomed version ofthe images above. Without transformations applied, it takes up the entirescreen. To achieve the "zoom" animation, this view's bounds are animatedfrom the bounds of the thumbnail button above, to its final laid-outbounds.--><ImageViewandroid:id="@+id/expanded_image"android:layout_width="match_parent"android:layout_height="match_parent"android:visibility="invisible"android:contentDescription="@string/description_zoom_touch_close" /></FrameLayout>

(2) 設(shè)置縮放動(dòng)畫
一旦應(yīng)用布局文件,即可設(shè)置出發(fā)縮放動(dòng)畫的事件。以下代碼增加View.onClickListener事件給ImageButton,如此,當(dāng)用戶點(diǎn)擊此按鈕時(shí)即實(shí)現(xiàn)縮放動(dòng)畫:

public class ZoomActivity extends FragmentActivity {// Hold a reference to the current animator,// so that it can be canceled mid-way.private Animator mCurrentAnimator;// The system "short" animation time duration, in milliseconds. This// duration is ideal for subtle animations or animations that occur// very frequently.private int mShortAnimationDuration;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_zoom);// Hook up clicks on the thumbnail views.final View thumb1View = findViewById(R.id.thumb_button_1);thumb1View.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {zoomImageFromThumb(thumb1View, R.drawable.image1);}});// Retrieve and cache the system's default "short" animation time.mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);}... }

(3) 縮放視圖
在恰當(dāng)?shù)臅r(shí)機(jī)下需要從正常尺寸的視圖縮放到某個(gè)尺寸的視圖。通常來講,需要根據(jù)視圖的邊界來動(dòng)畫。以下方法展示了怎么實(shí)現(xiàn)縮放動(dòng)畫(從縮略視圖到更大尺寸):
[1]. 分配高分辨率圖片到隱藏的“放大”(擴(kuò)大)ImageView中。以下的樣例代碼將一張大型圖片簡單的載入到用戶界面線程中。更科學(xué)的做法是在獨(dú)立的線程中載入圖片并在用戶界面線程中設(shè)置位圖以防止阻礙用戶界面線程。理想情況下,位圖不應(yīng)該比屏幕尺寸大。
[2]. 計(jì)算ImageView開始和結(jié)束時(shí)的邊界。
[3]. 根據(jù)開始邊界和結(jié)束邊界,同時(shí)動(dòng)畫四個(gè)位置和尺寸值X,Y(SCALE_X和SCALE_Y)。四個(gè)動(dòng)畫被增加到AnimatorSet中,這樣他們可以在同時(shí)開始。
[4]. 當(dāng)圖片被放大用戶再點(diǎn)擊屏幕時(shí)運(yùn)行類似的動(dòng)畫將視圖縮小(還原)。可以為ImageView增加View.onClickListerer來監(jiān)聽用戶的點(diǎn)擊。當(dāng)用戶點(diǎn)擊時(shí),ImageView會還原縮略圖大小并將其可見性設(shè)置為GONE來隱藏。

private void zoomImageFromThumb(final View thumbView, int imageResId) {// If there's an animation in progress, cancel it// immediately and proceed with this one.if (mCurrentAnimator != null) {mCurrentAnimator.cancel();}// Load the high-resolution "zoomed-in" image.final ImageView expandedImageView = (ImageView) findViewById(R.id.expanded_image);expandedImageView.setImageResource(imageResId);// Calculate the starting and ending bounds for the zoomed-in image.// This step involves lots of math. Yay, math.final Rect startBounds = new Rect();final Rect finalBounds = new Rect();final Point globalOffset = new Point();// The start bounds are the global visible rectangle of the thumbnail,// and the final bounds are the global visible rectangle of the container// view. Also set the container view's offset as the origin for the// bounds, since that's the origin for the positioning animation// properties (X, Y).thumbView.getGlobalVisibleRect(startBounds);findViewById(R.id.container).getGlobalVisibleRect(finalBounds, globalOffset);startBounds.offset(-globalOffset.x, -globalOffset.y);finalBounds.offset(-globalOffset.x, -globalOffset.y);// Adjust the start bounds to be the same aspect ratio as the final// bounds using the "center crop" technique. This prevents undesirable// stretching during the animation. Also calculate the start scaling// factor (the end scaling factor is always 1.0).float startScale;if ((float) finalBounds.width() / finalBounds.height()> (float) startBounds.width() / startBounds.height()) {// Extend start bounds horizontallystartScale = (float) startBounds.height() / finalBounds.height();float startWidth = startScale * finalBounds.width();float deltaWidth = (startWidth - startBounds.width()) / 2;startBounds.left -= deltaWidth;startBounds.right += deltaWidth;} else {// Extend start bounds verticallystartScale = (float) startBounds.width() / finalBounds.width();float startHeight = startScale * finalBounds.height();float deltaHeight = (startHeight - startBounds.height()) / 2;startBounds.top -= deltaHeight;startBounds.bottom += deltaHeight;}// Hide the thumbnail and show the zoomed-in view. When the animation// begins, it will position the zoomed-in view in the place of the// thumbnail.thumbView.setAlpha(0f);expandedImageView.setVisibility(View.VISIBLE);// Set the pivot point for SCALE_X and SCALE_Y transformations// to the top-left corner of the zoomed-in view (the default// is the center of the view).expandedImageView.setPivotX(0f);expandedImageView.setPivotY(0f);// Construct and run the parallel animation of the four translation and// scale properties (X, Y, SCALE_X, and SCALE_Y).AnimatorSet set = new AnimatorSet();set.play(ObjectAnimator.ofFloat(expandedImageView, View.X,startBounds.left, finalBounds.left)).with(ObjectAnimator.ofFloat(expandedImageView, View.Y,startBounds.top, finalBounds.top)).with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X,startScale, 1f)).with(ObjectAnimator.ofFloat(expandedImageView,View.SCALE_Y, startScale, 1f));set.setDuration(mShortAnimationDuration);set.setInterpolator(new DecelerateInterpolator());set.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {mCurrentAnimator = null;}@Overridepublic void onAnimationCancel(Animator animation) {mCurrentAnimator = null;}});set.start();mCurrentAnimator = set;// Upon clicking the zoomed-in image, it should zoom back down// to the original bounds and show the thumbnail instead of// the expanded image.final float startScaleFinal = startScale;expandedImageView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (mCurrentAnimator != null) {mCurrentAnimator.cancel();}// Animate the four positioning/sizing properties in parallel,// back to their original values.AnimatorSet set = new AnimatorSet();set.play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left)).with(ObjectAnimator.ofFloat(expandedImageView, View.Y,startBounds.top)).with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScaleFinal)).with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScaleFinal));set.setDuration(mShortAnimationDuration);set.setInterpolator(new DecelerateInterpolator());set.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {thumbView.setAlpha(1f);expandedImageView.setVisibility(View.GONE);mCurrentAnimator = null;}@Overridepublic void onAnimationCancel(Animator animation) {thumbView.setAlpha(1f);expandedImageView.setVisibility(View.GONE);mCurrentAnimator = null;}});set.start();mCurrentAnimator = set;}}); }

4.5 動(dòng)畫布局文件的改變

學(xué)習(xí)當(dāng)在布局文件中增加、移除以及更新子視圖時(shí)如何開啟內(nèi)建動(dòng)畫。

布局動(dòng)畫是對布局文件配置更改之前預(yù)先載入動(dòng)畫。開發(fā)者需要做的就是在布局文件中設(shè)置屬性來告知安卓系統(tǒng)動(dòng)畫改變布局文件的改變,系統(tǒng)用默認(rèn)的動(dòng)畫效果動(dòng)畫顯示它們。

提示:若欲實(shí)現(xiàn)自定義布局動(dòng)畫,需創(chuàng)建LayoutTransition對象并需通過setLayoutTransiton()方法將此對象應(yīng)用到布局文件中。

此處有一個(gè)當(dāng)增加列表中的條目時(shí)默認(rèn)的布局動(dòng)畫:http://developer.android.com/training/animation/anim_layout_changes.mp4
布局動(dòng)畫,點(diǎn)擊設(shè)備屏幕放映動(dòng)畫

若想直接看此部分的代碼樣例, 下載本節(jié)應(yīng)用程序并選擇Crossfade樣例,看以下文件中的代碼實(shí)現(xiàn):
[1]. src/LayoutChangesActivity.java
[2]. layout/activity_layout_changes.xml
[3]. menu/activity_layout_changes.xml

(1) 創(chuàng)建布局
在活動(dòng)的布局XML文件中,將布局文件中欲開啟的動(dòng)畫的android:animateLayoutChanges屬性設(shè)置為true。例:

<LinearLayout android:id="@+id/container"android:animateLayoutChanges="true"... />

(2) 從布局文件中增加、 更新或移除內(nèi)容
至此,所有需要做的操作就是增加、移除或更新布局文件中的內(nèi)容,布局文件中的內(nèi)容將會自動(dòng)的以動(dòng)畫形式實(shí)現(xiàn):

private ViewGroup mContainerView; ... private void addItem() {View newView;...mContainerView.addView(newView, 0); }

[2015.11.18-16:35]

總結(jié)

以上是生活随笔為你收集整理的pAdTy_1 构建图形和动画应用程序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

国产一精品一av一免费 | 红桃av一区二区三区在线无码av | 2020久久香蕉国产线看观看 | 国产在线一区二区三区四区五区 | 无码人妻精品一区二区三区下载 | 国产一区二区三区精品视频 | 欧美一区二区三区视频在线观看 | 成人性做爰aaa片免费看 | 成人无码精品一区二区三区 | 亚洲最大成人网站 | 亚洲熟妇色xxxxx欧美老妇 | 久久久久亚洲精品中文字幕 | 东北女人啪啪对白 | 国产av剧情md精品麻豆 | 国产精品a成v人在线播放 | 午夜福利不卡在线视频 | 亚洲熟妇自偷自拍另类 | 波多野结衣 黑人 | 狠狠躁日日躁夜夜躁2020 | 人人妻人人澡人人爽精品欧美 | 日日摸夜夜摸狠狠摸婷婷 | 国产成人无码午夜视频在线观看 | 伊人色综合久久天天小片 | 久久久精品456亚洲影院 | 日韩精品久久久肉伦网站 | 97人妻精品一区二区三区 | 日韩人妻无码一区二区三区久久99 | 在线天堂新版最新版在线8 | ass日本丰满熟妇pics | 色综合久久久无码中文字幕 | 国产人妻久久精品二区三区老狼 | 精品国产青草久久久久福利 | 丰满少妇女裸体bbw | 国产成人亚洲综合无码 | 丁香花在线影院观看在线播放 | 久久人人爽人人爽人人片av高清 | 爱做久久久久久 | av无码不卡在线观看免费 | 水蜜桃av无码 | 国产精品久久久久7777 | 精品欧洲av无码一区二区三区 | 人人爽人人澡人人人妻 | 综合激情五月综合激情五月激情1 | 狠狠躁日日躁夜夜躁2020 | 99er热精品视频 | 国产av人人夜夜澡人人爽麻豆 | 午夜免费福利小电影 | 亚洲中文字幕在线无码一区二区 | 久久综合给合久久狠狠狠97色 | 伊人久久大香线焦av综合影院 | 狠狠cao日日穞夜夜穞av | 强奷人妻日本中文字幕 | 国产九九九九九九九a片 | 欧美猛少妇色xxxxx | 色婷婷av一区二区三区之红樱桃 | 18禁黄网站男男禁片免费观看 | 国产猛烈高潮尖叫视频免费 | 日本一卡2卡3卡四卡精品网站 | 国产精品国产自线拍免费软件 | 亚洲 欧美 激情 小说 另类 | 日本又色又爽又黄的a片18禁 | 麻豆人妻少妇精品无码专区 | 中国女人内谢69xxxxxa片 | 日本www一道久久久免费榴莲 | 精品日本一区二区三区在线观看 | 任你躁在线精品免费 | 中文字幕无码日韩专区 | 性史性农村dvd毛片 | 国产精品久久久久影院嫩草 | 领导边摸边吃奶边做爽在线观看 | 精品成在人线av无码免费看 | 蜜臀aⅴ国产精品久久久国产老师 | 亚洲日韩乱码中文无码蜜桃臀网站 | 色诱久久久久综合网ywww | 97人妻精品一区二区三区 | 国产精品久久久久影院嫩草 | 人人妻人人藻人人爽欧美一区 | 中文字幕乱码中文乱码51精品 | 丰满人妻一区二区三区免费视频 | 野狼第一精品社区 | 在线欧美精品一区二区三区 | 少妇性荡欲午夜性开放视频剧场 | 国产麻豆精品一区二区三区v视界 | 天天躁日日躁狠狠躁免费麻豆 | 澳门永久av免费网站 | 男女爱爱好爽视频免费看 | 天堂一区人妻无码 | 久久aⅴ免费观看 | 国产成人综合在线女婷五月99播放 | 免费观看黄网站 | 中文字幕无码av波多野吉衣 | 久久久久久久久888 | 国产片av国语在线观看 | 99riav国产精品视频 | 国产成人精品必看 | 亚洲色欲久久久综合网东京热 | av人摸人人人澡人人超碰下载 | 欧美丰满少妇xxxx性 | 捆绑白丝粉色jk震动捧喷白浆 | 国产成人综合在线女婷五月99播放 | 欧美阿v高清资源不卡在线播放 | 亚洲日韩av一区二区三区中文 | 亚洲国产精品久久久天堂 | 真人与拘做受免费视频 | 欧美zoozzooz性欧美 | 无码av免费一区二区三区试看 | 国产卡一卡二卡三 | 午夜精品久久久内射近拍高清 | 99精品无人区乱码1区2区3区 | 最近免费中文字幕中文高清百度 | 国模大胆一区二区三区 | 色偷偷人人澡人人爽人人模 | 精品夜夜澡人妻无码av蜜桃 | 国产亚洲人成a在线v网站 | 久久久精品欧美一区二区免费 | 亚洲一区二区三区无码久久 | 性色av无码免费一区二区三区 | 六月丁香婷婷色狠狠久久 | 啦啦啦www在线观看免费视频 | 中文字幕无线码免费人妻 | 成人欧美一区二区三区黑人 | 激情五月综合色婷婷一区二区 | 久久久久人妻一区精品色欧美 | 中文字幕无码乱人伦 | 成熟女人特级毛片www免费 | 亚洲色大成网站www | 亚洲中文字幕无码中文字在线 | 熟妇人妻无乱码中文字幕 | 欧美日韩一区二区综合 | 少妇无套内谢久久久久 | 国内老熟妇对白xxxxhd | 日本熟妇人妻xxxxx人hd | 色五月五月丁香亚洲综合网 | 成在人线av无码免观看麻豆 | 77777熟女视频在线观看 а天堂中文在线官网 | 国产精品毛多多水多 | 99久久婷婷国产综合精品青草免费 | 99久久久无码国产aaa精品 | 亚洲精品久久久久avwww潮水 | 亚洲爆乳大丰满无码专区 | 亚洲a无码综合a国产av中文 | 久久精品无码一区二区三区 | 久久综合九色综合97网 | 中文字幕日韩精品一区二区三区 | 国产色xx群视频射精 | 老熟女重囗味hdxx69 | 国产特级毛片aaaaaaa高清 | 国产激情无码一区二区app | 伊人久久大香线蕉av一区二区 | 婷婷五月综合缴情在线视频 | 理论片87福利理论电影 | 久久国产精品二国产精品 | 最新国产麻豆aⅴ精品无码 | 久久国产劲爆∧v内射 | 婷婷色婷婷开心五月四房播播 | 乱码av麻豆丝袜熟女系列 | 在线观看免费人成视频 | 色综合久久久无码网中文 | 永久黄网站色视频免费直播 | 国产特级毛片aaaaaa高潮流水 | 色欲av亚洲一区无码少妇 | 久久久久人妻一区精品色欧美 | 欧美三级a做爰在线观看 | 天堂久久天堂av色综合 | 亚洲高清偷拍一区二区三区 | 免费国产成人高清在线观看网站 | 无码人妻丰满熟妇区五十路百度 | 国产后入清纯学生妹 | 男女爱爱好爽视频免费看 | 精品一区二区三区无码免费视频 | 午夜时刻免费入口 | 天天综合网天天综合色 | 四十如虎的丰满熟妇啪啪 | 天天摸天天碰天天添 | 在线 国产 欧美 亚洲 天堂 | 性啪啪chinese东北女人 | 无套内谢的新婚少妇国语播放 | 午夜福利一区二区三区在线观看 | 亚洲成av人在线观看网址 | 亚洲伊人久久精品影院 | 日产国产精品亚洲系列 | 76少妇精品导航 | 窝窝午夜理论片影院 | 久久久久免费看成人影片 | 99麻豆久久久国产精品免费 | 少妇厨房愉情理9仑片视频 | 国精产品一品二品国精品69xx | 成人一在线视频日韩国产 | 国产美女极度色诱视频www | 久久久久免费看成人影片 | 天天摸天天碰天天添 | 成人女人看片免费视频放人 | 久久精品国产日本波多野结衣 | 欧洲vodafone精品性 | 麻豆精品国产精华精华液好用吗 | 一本色道婷婷久久欧美 | 日韩欧美中文字幕在线三区 | 国产免费久久久久久无码 | 日韩精品成人一区二区三区 | 内射老妇bbwx0c0ck | 国产人妻人伦精品 | 亚洲成av人影院在线观看 | 免费观看的无遮挡av | 熟女俱乐部五十路六十路av | 久青草影院在线观看国产 | 国产亚洲美女精品久久久2020 | 国产亚洲精品精品国产亚洲综合 | 久久熟妇人妻午夜寂寞影院 | 国产在线一区二区三区四区五区 | 99久久久无码国产aaa精品 | 97色伦图片97综合影院 | 少妇愉情理伦片bd | 无码人妻出轨黑人中文字幕 | 欧美性色19p | 婷婷丁香六月激情综合啪 | 亚洲狠狠婷婷综合久久 | 乱码av麻豆丝袜熟女系列 | 中文无码伦av中文字幕 | 鲁鲁鲁爽爽爽在线视频观看 | 乱码av麻豆丝袜熟女系列 | 久久99久久99精品中文字幕 | 99久久久国产精品无码免费 | 俄罗斯老熟妇色xxxx | 综合人妻久久一区二区精品 | 久久久久免费精品国产 | 鲁一鲁av2019在线 | 青青青手机频在线观看 | 久久精品人人做人人综合 | 真人与拘做受免费视频一 | 久久99久久99精品中文字幕 | 巨爆乳无码视频在线观看 | а√天堂www在线天堂小说 | 国产色视频一区二区三区 | 两性色午夜免费视频 | 国产精品第一国产精品 | 午夜精品一区二区三区在线观看 | 亚洲理论电影在线观看 | 亚洲成a人片在线观看无码 | 国产无遮挡又黄又爽又色 | 免费看少妇作爱视频 | 在线精品国产一区二区三区 | 欧美日韩视频无码一区二区三 | 亚洲一区二区三区播放 | 又大又硬又黄的免费视频 | 亚洲の无码国产の无码步美 | 漂亮人妻洗澡被公强 日日躁 | 九九久久精品国产免费看小说 | 国产精品资源一区二区 | 亚洲中文字幕无码中文字在线 | 国产日产欧产精品精品app | 四十如虎的丰满熟妇啪啪 | 国产午夜福利亚洲第一 | 麻豆国产97在线 | 欧洲 | 天堂无码人妻精品一区二区三区 | 中文字幕人妻无码一夲道 | 国产欧美熟妇另类久久久 | 成人影院yy111111在线观看 | 99久久人妻精品免费二区 | 成年女人永久免费看片 | 精品国产国产综合精品 | 国产精品-区区久久久狼 | 色婷婷欧美在线播放内射 | 国产婷婷色一区二区三区在线 | 中文字幕无码热在线视频 | 无码帝国www无码专区色综合 | 捆绑白丝粉色jk震动捧喷白浆 | 无遮挡国产高潮视频免费观看 | 国产人妻人伦精品 | 亚洲国产精品久久久久久 | 熟女体下毛毛黑森林 | 天天av天天av天天透 | 无码人妻精品一区二区三区下载 | 中文字幕无码日韩欧毛 | 亚洲成a人片在线观看无码3d | 免费观看又污又黄的网站 | 日日夜夜撸啊撸 | 国产成人久久精品流白浆 | 亚洲国产一区二区三区在线观看 | 国产精品久久久午夜夜伦鲁鲁 | 给我免费的视频在线观看 | 国产av剧情md精品麻豆 | 国产舌乚八伦偷品w中 | 国产精品丝袜黑色高跟鞋 | 亚洲人成影院在线无码按摩店 | 真人与拘做受免费视频一 | 国产人成高清在线视频99最全资源 | 亚洲春色在线视频 | 全球成人中文在线 | 999久久久国产精品消防器材 | 爱做久久久久久 | 日韩人妻无码中文字幕视频 | 野外少妇愉情中文字幕 | 国产av一区二区三区最新精品 | 亚洲精品国产精品乱码视色 | 精品久久久无码中文字幕 | 亚洲の无码国产の无码影院 | 色综合天天综合狠狠爱 | 无码国产激情在线观看 | 日本免费一区二区三区最新 | 领导边摸边吃奶边做爽在线观看 | 成人综合网亚洲伊人 | а天堂中文在线官网 | 亚洲精品国产a久久久久久 | 伊在人天堂亚洲香蕉精品区 | 扒开双腿疯狂进出爽爽爽视频 | 亚洲中文字幕va福利 | 最近的中文字幕在线看视频 | 日本一区二区三区免费播放 | 377p欧洲日本亚洲大胆 | 十八禁真人啪啪免费网站 | 亚洲色欲色欲天天天www | 精品成在人线av无码免费看 | 国产熟妇另类久久久久 | 久久午夜无码鲁丝片 | 日韩av无码中文无码电影 | 国模大胆一区二区三区 | 成熟妇人a片免费看网站 | 18禁止看的免费污网站 | 欧美日韩色另类综合 | 国产精品无码一区二区三区不卡 | 中国女人内谢69xxxxxa片 | 夜精品a片一区二区三区无码白浆 | 无码精品人妻一区二区三区av | 免费看男女做好爽好硬视频 | 欧美亚洲国产一区二区三区 | 中文字幕乱妇无码av在线 | 国产又爽又猛又粗的视频a片 | 丰满少妇女裸体bbw | 国产精品美女久久久久av爽李琼 | 激情内射日本一区二区三区 | 一个人免费观看的www视频 | 久久久久久久人妻无码中文字幕爆 | 欧美人与善在线com | 亚洲一区二区三区四区 | 综合网日日天干夜夜久久 | 国产激情一区二区三区 | 东京热一精品无码av | 国内揄拍国内精品人妻 | 高潮毛片无遮挡高清免费 | 亚洲综合伊人久久大杳蕉 | 午夜精品久久久内射近拍高清 | 欧美变态另类xxxx | 欧美性猛交内射兽交老熟妇 | 丰满人妻一区二区三区免费视频 | 亚洲成色在线综合网站 | 欧美三级不卡在线观看 | 亚洲欧美国产精品久久 | 熟妇人妻无乱码中文字幕 | 少妇久久久久久人妻无码 | 久久久久av无码免费网 | 麻豆蜜桃av蜜臀av色欲av | 亚洲国产高清在线观看视频 | 99久久人妻精品免费一区 | 丰满少妇高潮惨叫视频 | 国产精品无码一区二区桃花视频 | 午夜免费福利小电影 | 好爽又高潮了毛片免费下载 | 又大又紧又粉嫩18p少妇 | 精品一区二区不卡无码av | 奇米影视7777久久精品人人爽 | 亚洲精品中文字幕乱码 | 欧美人与物videos另类 | 高清国产亚洲精品自在久久 | 欧美日韩一区二区三区自拍 | 亚洲国产欧美在线成人 | 亚洲熟妇色xxxxx欧美老妇 | 偷窥村妇洗澡毛毛多 | 日韩精品a片一区二区三区妖精 | 东京一本一道一二三区 | 欧美性色19p | 久久精品国产一区二区三区 | 亚洲国产成人a精品不卡在线 | 国产精品欧美成人 | 亚洲午夜福利在线观看 | 亚洲熟妇色xxxxx欧美老妇 | 青春草在线视频免费观看 | 久久99精品国产麻豆 | 欧美老妇交乱视频在线观看 | 天堂无码人妻精品一区二区三区 | 国产亚洲欧美日韩亚洲中文色 | 中文字幕日产无线码一区 | 亚洲精品www久久久 | 一区二区三区乱码在线 | 欧洲 | 中文亚洲成a人片在线观看 | 成人片黄网站色大片免费观看 | 国产尤物精品视频 | 国产精品理论片在线观看 | 欧美老人巨大xxxx做受 | 99麻豆久久久国产精品免费 | 精品亚洲韩国一区二区三区 | 国产成人一区二区三区别 | 正在播放东北夫妻内射 | 日本免费一区二区三区最新 | 少妇性荡欲午夜性开放视频剧场 | 亚洲色大成网站www国产 | av无码不卡在线观看免费 | 色婷婷久久一区二区三区麻豆 | 亚洲一区av无码专区在线观看 | 久久综合网欧美色妞网 | 爱做久久久久久 | 国产精品99爱免费视频 | 东京无码熟妇人妻av在线网址 | 四虎国产精品一区二区 | 久久亚洲日韩精品一区二区三区 | 亚洲综合另类小说色区 | 日韩欧美群交p片內射中文 | 欧洲熟妇精品视频 | 欧美人与物videos另类 | 国语自产偷拍精品视频偷 | 久久精品国产一区二区三区 | 国产情侣作爱视频免费观看 | 无码人妻久久一区二区三区不卡 | 亚洲欧美日韩成人高清在线一区 | 亚洲色成人中文字幕网站 | 国产区女主播在线观看 | 国产香蕉97碰碰久久人人 | 色综合天天综合狠狠爱 | 日日碰狠狠丁香久燥 | 久久精品国产一区二区三区肥胖 | 国产精品无套呻吟在线 | 天堂а√在线中文在线 | 人人妻人人澡人人爽欧美一区 | 欧美日韩一区二区三区自拍 | 成人片黄网站色大片免费观看 | 免费人成在线观看网站 | 日韩精品久久久肉伦网站 | 国产人妻大战黑人第1集 | 色综合视频一区二区三区 | 久久99精品久久久久久动态图 | 亚洲色www成人永久网址 | 久久久成人毛片无码 | 精品国产av色一区二区深夜久久 | 成人动漫在线观看 | 超碰97人人做人人爱少妇 | 亚洲国产午夜精品理论片 | 中文字幕av日韩精品一区二区 | 亚洲一区二区三区 | a在线亚洲男人的天堂 | 亚洲色大成网站www国产 | 俄罗斯老熟妇色xxxx | 久9re热视频这里只有精品 | 国产精品久久精品三级 | 久久人人爽人人人人片 | 少妇人妻av毛片在线看 | 无码精品国产va在线观看dvd | 国产偷抇久久精品a片69 | 国产后入清纯学生妹 | 中文字幕久久久久人妻 | 国产午夜无码精品免费看 | 免费观看又污又黄的网站 | 动漫av网站免费观看 | 蜜臀aⅴ国产精品久久久国产老师 | аⅴ资源天堂资源库在线 | 国产精品国产三级国产专播 | 麻豆精品国产精华精华液好用吗 | 国内精品久久毛片一区二区 | 午夜理论片yy44880影院 | 日本高清一区免费中文视频 | 欧美成人高清在线播放 | 亚洲中文字幕av在天堂 | 国产精品久久久久久久影院 | av在线亚洲欧洲日产一区二区 | 国产办公室秘书无码精品99 | 国产女主播喷水视频在线观看 | 欧美日本精品一区二区三区 | 人人澡人人妻人人爽人人蜜桃 | 亚洲色欲色欲欲www在线 | 久久精品无码一区二区三区 | 亚洲人成影院在线无码按摩店 | 久久无码中文字幕免费影院蜜桃 | 99久久精品日本一区二区免费 | 国产精品久久久午夜夜伦鲁鲁 | 奇米影视7777久久精品 | 人人妻人人藻人人爽欧美一区 | 成熟女人特级毛片www免费 | 领导边摸边吃奶边做爽在线观看 | 久久天天躁狠狠躁夜夜免费观看 | 真人与拘做受免费视频 | 日本乱偷人妻中文字幕 | 亚洲а∨天堂久久精品2021 | 国产成人无码a区在线观看视频app | 午夜无码人妻av大片色欲 | 老熟女乱子伦 | 一本大道久久东京热无码av | 成熟妇人a片免费看网站 | 欧美激情一区二区三区成人 | 国产成人精品视频ⅴa片软件竹菊 | 精品欧洲av无码一区二区三区 | 成人片黄网站色大片免费观看 | 欧美激情综合亚洲一二区 | 国产成人精品久久亚洲高清不卡 | 最近免费中文字幕中文高清百度 | 亚洲中文字幕va福利 | 亚洲小说图区综合在线 | 精品无人国产偷自产在线 | 久久久久久国产精品无码下载 | 九九综合va免费看 | 免费中文字幕日韩欧美 | 高潮毛片无遮挡高清免费 | 国产综合久久久久鬼色 | 男女超爽视频免费播放 | 国产香蕉97碰碰久久人人 | 四虎永久在线精品免费网址 | av无码电影一区二区三区 | 国产午夜视频在线观看 | 无套内谢的新婚少妇国语播放 | 国产成人无码a区在线观看视频app | 国产午夜视频在线观看 | 牲欲强的熟妇农村老妇女视频 | 成人影院yy111111在线观看 | 婷婷丁香六月激情综合啪 | 爱做久久久久久 | 国产又粗又硬又大爽黄老大爷视 | 欧美国产日韩久久mv | 高清不卡一区二区三区 | 国产小呦泬泬99精品 | 国产成人综合在线女婷五月99播放 | 精品人妻人人做人人爽夜夜爽 | 国精产品一品二品国精品69xx | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 国产电影无码午夜在线播放 | 久久久久久久女国产乱让韩 | 久久久精品456亚洲影院 | 狠狠色欧美亚洲狠狠色www | a在线观看免费网站大全 | 99久久婷婷国产综合精品青草免费 | 风流少妇按摩来高潮 | 双乳奶水饱满少妇呻吟 | 亚洲中文字幕乱码av波多ji | 亚洲日韩中文字幕在线播放 | 丝袜足控一区二区三区 | 精品国产国产综合精品 | 波多野结衣av一区二区全免费观看 | 国产偷国产偷精品高清尤物 | 亚洲狠狠婷婷综合久久 | 漂亮人妻洗澡被公强 日日躁 | 成人欧美一区二区三区 | 欧美真人作爱免费视频 | 四虎影视成人永久免费观看视频 | 欧美黑人性暴力猛交喷水 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 亚洲人成影院在线观看 | 久久国产精品萌白酱免费 | 亚洲一区二区三区国产精华液 | 乱人伦人妻中文字幕无码久久网 | 国产精品理论片在线观看 | 双乳奶水饱满少妇呻吟 | 俺去俺来也www色官网 | 欧美激情综合亚洲一二区 | 国产成人精品一区二区在线小狼 | 天堂亚洲免费视频 | 日本精品人妻无码77777 天堂一区人妻无码 | 中文亚洲成a人片在线观看 | 熟妇人妻中文av无码 | 色欲久久久天天天综合网精品 | 国产乱子伦视频在线播放 | 久精品国产欧美亚洲色aⅴ大片 | 亚欧洲精品在线视频免费观看 | 我要看www免费看插插视频 | 在线成人www免费观看视频 | 美女张开腿让人桶 | 乌克兰少妇xxxx做受 | av小次郎收藏 | 久久精品国产大片免费观看 | www国产亚洲精品久久久日本 | 女人被男人爽到呻吟的视频 | 国产精品福利视频导航 | 樱花草在线播放免费中文 | 2020久久香蕉国产线看观看 | 男女性色大片免费网站 | 欧美精品一区二区精品久久 | 久久综合九色综合欧美狠狠 | 好屌草这里只有精品 | 国产精品爱久久久久久久 | 国产精品永久免费视频 | 精品日本一区二区三区在线观看 | 中文字幕亚洲情99在线 | 麻豆蜜桃av蜜臀av色欲av | 久久亚洲a片com人成 | 欧洲欧美人成视频在线 | 无码人妻精品一区二区三区下载 | 国产精品美女久久久久av爽李琼 | 精品水蜜桃久久久久久久 | 99国产精品白浆在线观看免费 | 国产一区二区三区影院 | 红桃av一区二区三区在线无码av | 草草网站影院白丝内射 | 天堂亚洲免费视频 | 国产亚洲人成a在线v网站 | 色欲久久久天天天综合网精品 | 欧美第一黄网免费网站 | 久久久久久久女国产乱让韩 | 小鲜肉自慰网站xnxx | 图片小说视频一区二区 | 久久99精品国产麻豆蜜芽 | 国产在热线精品视频 | 男人和女人高潮免费网站 | 国产精品办公室沙发 | 99久久人妻精品免费一区 | 日韩视频 中文字幕 视频一区 | 荫蒂添的好舒服视频囗交 | 国产乱子伦视频在线播放 | 亚洲精品国产a久久久久久 | 久久天天躁狠狠躁夜夜免费观看 | 国产人妻人伦精品1国产丝袜 | 免费看少妇作爱视频 | 中文字幕乱码亚洲无线三区 | 漂亮人妻洗澡被公强 日日躁 | 日韩亚洲欧美中文高清在线 | 亚洲国产精品无码久久久久高潮 | 午夜嘿嘿嘿影院 | 亚洲欧美中文字幕5发布 | 无码人妻精品一区二区三区下载 | 亚洲精品无码国产 | 人人澡人人妻人人爽人人蜜桃 | 亚洲s码欧洲m码国产av | 熟妇人妻无码xxx视频 | 亚洲自偷自拍另类第1页 | 国产精品.xx视频.xxtv | 成人亚洲精品久久久久软件 | 亚洲精品一区二区三区在线观看 | 久久久精品成人免费观看 | 天天爽夜夜爽夜夜爽 | 亚洲 高清 成人 动漫 | 亚洲精品一区二区三区在线 | 久久久久se色偷偷亚洲精品av | 国产免费观看黄av片 | 亚洲成av人片在线观看无码不卡 | 日韩欧美中文字幕在线三区 | 熟妇人妻无码xxx视频 | 成人无码精品一区二区三区 | 成人片黄网站色大片免费观看 | 国产精品久久久久久久影院 | 中文无码成人免费视频在线观看 | 久久精品国产一区二区三区 | 日韩人妻无码中文字幕视频 | 又紧又大又爽精品一区二区 | 久久综合久久自在自线精品自 | 乌克兰少妇xxxx做受 | 亚洲爆乳精品无码一区二区三区 | 2019午夜福利不卡片在线 | 国产精品高潮呻吟av久久 | 2020最新国产自产精品 | 亚洲va欧美va天堂v国产综合 | 国产va免费精品观看 | 青青久在线视频免费观看 | 日本护士毛茸茸高潮 | 久久人人爽人人人人片 | 伊人久久大香线焦av综合影院 | 亚洲日本va中文字幕 | 在线观看免费人成视频 | 亚洲精品一区二区三区四区五区 | 亚洲欧美日韩国产精品一区二区 | 国产高清不卡无码视频 | 荫蒂添的好舒服视频囗交 | 久久精品人人做人人综合试看 | 丰满少妇弄高潮了www | 2020久久超碰国产精品最新 | 国产九九九九九九九a片 | 精品人妻av区 | 4hu四虎永久在线观看 | 日本一本二本三区免费 | 精品国产一区二区三区四区 | 国内揄拍国内精品人妻 | 久久久久人妻一区精品色欧美 | 中文久久乱码一区二区 | 久久精品国产精品国产精品污 | 乱码午夜-极国产极内射 | 综合网日日天干夜夜久久 | 狠狠综合久久久久综合网 | 荫蒂被男人添的好舒服爽免费视频 | 欧美自拍另类欧美综合图片区 | 精品一区二区三区波多野结衣 | 性欧美熟妇videofreesex | 久久精品国产精品国产精品污 | 精品成在人线av无码免费看 | 国产成人无码av一区二区 | 爽爽影院免费观看 | 又湿又紧又大又爽a视频国产 | 国产精品久久久久影院嫩草 | 大肉大捧一进一出好爽视频 | 国产精品久久久久久久9999 | 亚洲区小说区激情区图片区 | 日日碰狠狠丁香久燥 | 色婷婷久久一区二区三区麻豆 | 中文字幕无码乱人伦 | 亚洲午夜久久久影院 | 久久国内精品自在自线 | 亚洲精品午夜无码电影网 | 1000部夫妻午夜免费 | 免费看男女做好爽好硬视频 | 日本精品人妻无码77777 天堂一区人妻无码 | 欧美日本精品一区二区三区 | 人妻少妇精品视频专区 | 日韩在线不卡免费视频一区 | 亚洲 a v无 码免 费 成 人 a v | 少妇激情av一区二区 | 亚洲精品欧美二区三区中文字幕 | 俺去俺来也www色官网 | 内射后入在线观看一区 | 亚洲精品午夜国产va久久成人 | 精品久久8x国产免费观看 | 亚洲精品欧美二区三区中文字幕 | 无码av中文字幕免费放 | 无码av岛国片在线播放 | 国产明星裸体无码xxxx视频 | 国产成人无码午夜视频在线观看 | av无码电影一区二区三区 | 精品熟女少妇av免费观看 | 精品夜夜澡人妻无码av蜜桃 | 国产一精品一av一免费 | 精品水蜜桃久久久久久久 | 国产色精品久久人妻 | 激情内射日本一区二区三区 | 精品国偷自产在线 | 久久久国产精品无码免费专区 | 国产艳妇av在线观看果冻传媒 | 精品国产aⅴ无码一区二区 | 国产精品久久久久久亚洲毛片 | 国语精品一区二区三区 | 国产精品免费大片 | 亚洲 日韩 欧美 成人 在线观看 | 99精品久久毛片a片 | 久久无码人妻影院 | 精品久久8x国产免费观看 | 无码成人精品区在线观看 | 高潮毛片无遮挡高清免费视频 | 亚洲午夜福利在线观看 | 欧美 丝袜 自拍 制服 另类 | 精品少妇爆乳无码av无码专区 | 国产在线精品一区二区高清不卡 | 7777奇米四色成人眼影 | 日本一区二区三区免费高清 | 熟妇人妻激情偷爽文 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 亚洲爆乳大丰满无码专区 | 国产精华av午夜在线观看 | 日本乱人伦片中文三区 | 婷婷色婷婷开心五月四房播播 | 色情久久久av熟女人妻网站 | 在线 国产 欧美 亚洲 天堂 | 国产一区二区三区影院 | 99久久久国产精品无码免费 | 老头边吃奶边弄进去呻吟 | 亚洲 a v无 码免 费 成 人 a v | 性欧美熟妇videofreesex | 欧美大屁股xxxxhd黑色 | 鲁一鲁av2019在线 | 国产精品无码mv在线观看 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 蜜臀av无码人妻精品 | 夜精品a片一区二区三区无码白浆 | 色婷婷av一区二区三区之红樱桃 | 中文字幕av无码一区二区三区电影 | 国产精品无码一区二区桃花视频 | 最新国产乱人伦偷精品免费网站 | 国语自产偷拍精品视频偷 | 无码乱肉视频免费大全合集 | 麻花豆传媒剧国产免费mv在线 | 欧美三级不卡在线观看 | 精品 日韩 国产 欧美 视频 | 欧美喷潮久久久xxxxx | 妺妺窝人体色www婷婷 | 日韩视频 中文字幕 视频一区 | 小鲜肉自慰网站xnxx | 成人性做爰aaa片免费看 | 久久视频在线观看精品 | 日本又色又爽又黄的a片18禁 | 国产成人av免费观看 | 东京热一精品无码av | 国模大胆一区二区三区 | 成人毛片一区二区 | 色欲综合久久中文字幕网 | 激情综合激情五月俺也去 | 亚洲欧美色中文字幕在线 | 亚洲中文字幕av在天堂 | 亚洲国产精品久久久天堂 | 牲欲强的熟妇农村老妇女 | 搡女人真爽免费视频大全 | 鲁大师影院在线观看 | 曰本女人与公拘交酡免费视频 | 成人一区二区免费视频 | 麻豆国产97在线 | 欧洲 | 无码人妻精品一区二区三区不卡 | 少妇人妻av毛片在线看 | 国产人妻精品午夜福利免费 | 人妻少妇精品无码专区二区 | 最近免费中文字幕中文高清百度 | 久久午夜夜伦鲁鲁片无码免费 | 学生妹亚洲一区二区 | 丝袜美腿亚洲一区二区 | 午夜精品久久久内射近拍高清 | 精品无码国产自产拍在线观看蜜 | 精品国偷自产在线 | 51国偷自产一区二区三区 | 亚洲人成网站色7799 | 丰满人妻翻云覆雨呻吟视频 | 无码人妻丰满熟妇区毛片18 | 丰满少妇高潮惨叫视频 | 最新国产麻豆aⅴ精品无码 | 久久国产劲爆∧v内射 | a国产一区二区免费入口 | 亚洲人成人无码网www国产 | 99国产精品白浆在线观看免费 | 国产麻豆精品一区二区三区v视界 | 国产精品亚洲五月天高清 | 亚洲一区二区三区香蕉 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | av在线亚洲欧洲日产一区二区 | 精品久久久无码人妻字幂 | 欧美丰满熟妇xxxx | 性做久久久久久久久 | 丁香花在线影院观看在线播放 | 无码国产色欲xxxxx视频 | 日日干夜夜干 | 亚洲性无码av中文字幕 | 永久免费观看美女裸体的网站 | 九九综合va免费看 | 亚洲理论电影在线观看 | 在线播放无码字幕亚洲 | 欧美成人免费全部网站 | 精品久久久无码中文字幕 | 中文精品久久久久人妻不卡 | 麻豆国产97在线 | 欧洲 | 成人精品天堂一区二区三区 | 人妻少妇被猛烈进入中文字幕 | 精品无码国产自产拍在线观看蜜 | 少女韩国电视剧在线观看完整 | 草草网站影院白丝内射 | 成人欧美一区二区三区 | 亚洲精品国产a久久久久久 | 人妻与老人中文字幕 | 老熟妇仑乱视频一区二区 | 国产疯狂伦交大片 | 亚洲中文字幕乱码av波多ji | 久久97精品久久久久久久不卡 | 国产精品a成v人在线播放 | 日本熟妇乱子伦xxxx | 欧美性猛交内射兽交老熟妇 | 国产亚洲欧美在线专区 | 精品久久久无码人妻字幂 | 理论片87福利理论电影 | 亚洲精品成a人在线观看 | 无码人中文字幕 | 少妇人妻av毛片在线看 | 欧美人与牲动交xxxx | 亚洲国产精品无码一区二区三区 | 国产免费久久精品国产传媒 | 亚洲精品一区二区三区在线 | 色欲久久久天天天综合网精品 | 国产无遮挡又黄又爽免费视频 | 久久久无码中文字幕久... | 国产99久久精品一区二区 | 狠狠躁日日躁夜夜躁2020 | 国产精品第一国产精品 | 国内精品人妻无码久久久影院 | 中文字幕无码日韩欧毛 | 无码av中文字幕免费放 | 国产精品美女久久久 | 国产乱人伦av在线无码 | 亚洲人亚洲人成电影网站色 | 亚洲自偷自偷在线制服 | 一本大道久久东京热无码av | 国产片av国语在线观看 | 99视频精品全部免费免费观看 | 国精品人妻无码一区二区三区蜜柚 | 精品国产一区二区三区av 性色 | 亚洲精品国偷拍自产在线麻豆 | 特大黑人娇小亚洲女 | 99久久精品无码一区二区毛片 | 国产小呦泬泬99精品 | a在线观看免费网站大全 | 青草视频在线播放 | 久久久久久久人妻无码中文字幕爆 | 国产欧美熟妇另类久久久 | 精品国产aⅴ无码一区二区 | 大乳丰满人妻中文字幕日本 | 国产成人午夜福利在线播放 | 国产精品亚洲专区无码不卡 | 激情爆乳一区二区三区 | 国产区女主播在线观看 | 熟妇人妻无乱码中文字幕 | 中文字幕+乱码+中文字幕一区 | 国产精品资源一区二区 | 久久久精品456亚洲影院 | 无码一区二区三区在线 | 亚洲综合色区中文字幕 | 成人亚洲精品久久久久软件 | 精品人妻人人做人人爽 | 强开小婷嫩苞又嫩又紧视频 | 亚洲a无码综合a国产av中文 | 亚洲精品中文字幕乱码 | 欧美放荡的少妇 | 国产精品美女久久久久av爽李琼 | 国精产品一区二区三区 | 国产片av国语在线观看 | 国产舌乚八伦偷品w中 | 国产亚洲欧美日韩亚洲中文色 | 日韩无套无码精品 | 成熟女人特级毛片www免费 | 国产欧美精品一区二区三区 | a片免费视频在线观看 | 成人亚洲精品久久久久 | 成人试看120秒体验区 | 午夜精品久久久内射近拍高清 | 精品久久综合1区2区3区激情 | 丰满肥臀大屁股熟妇激情视频 | 色一情一乱一伦一区二区三欧美 | 大乳丰满人妻中文字幕日本 | 青青青手机频在线观看 | 久久国产精品精品国产色婷婷 | 领导边摸边吃奶边做爽在线观看 | 国产一精品一av一免费 | 国产莉萝无码av在线播放 | 亚洲乱亚洲乱妇50p | 色婷婷久久一区二区三区麻豆 | 国产美女精品一区二区三区 | 亚洲色欲色欲欲www在线 | 美女扒开屁股让男人桶 | 国产精品自产拍在线观看 | √天堂中文官网8在线 | 日本一区二区更新不卡 | 精品久久久无码中文字幕 | 在线欧美精品一区二区三区 | 夜夜影院未满十八勿进 | 曰本女人与公拘交酡免费视频 | 亚洲国产精品无码久久久久高潮 | 久久97精品久久久久久久不卡 | 欧美亚洲国产一区二区三区 | 久久精品视频在线看15 | 精品无人国产偷自产在线 | 色一情一乱一伦一区二区三欧美 | 呦交小u女精品视频 | 一个人看的视频www在线 | 国语自产偷拍精品视频偷 | 最新国产麻豆aⅴ精品无码 | 欧美第一黄网免费网站 | 成人三级无码视频在线观看 | 男人的天堂2018无码 | 少妇邻居内射在线 | 国产成人无码a区在线观看视频app | 亚洲熟悉妇女xxx妇女av | 少妇太爽了在线观看 | 亚洲欧美综合区丁香五月小说 | 97人妻精品一区二区三区 | 中文无码伦av中文字幕 | 在线а√天堂中文官网 | 成人免费无码大片a毛片 | 亚洲熟妇色xxxxx欧美老妇y | 人人妻人人澡人人爽精品欧美 | 女人被爽到呻吟gif动态图视看 | 亚洲中文字幕乱码av波多ji | 黄网在线观看免费网站 | 国产精品嫩草久久久久 | 亚洲中文字幕在线无码一区二区 | 无码纯肉视频在线观看 | 中文字幕乱妇无码av在线 | 国产9 9在线 | 中文 | 亚洲人成人无码网www国产 | 啦啦啦www在线观看免费视频 | 亚洲国产精品久久久天堂 | 熟妇人妻中文av无码 | 久精品国产欧美亚洲色aⅴ大片 | 日日碰狠狠躁久久躁蜜桃 | 99er热精品视频 | 日韩精品无码一本二本三本色 | 蜜臀aⅴ国产精品久久久国产老师 | 亚洲国产av美女网站 | 强伦人妻一区二区三区视频18 | 久久国产精品偷任你爽任你 | 亚洲日韩乱码中文无码蜜桃臀网站 | 小泽玛莉亚一区二区视频在线 | 久久国产精品二国产精品 | 国产偷国产偷精品高清尤物 | 国产三级精品三级男人的天堂 | 大色综合色综合网站 | 久久99精品久久久久婷婷 | 日韩欧美成人免费观看 | 欧美xxxxx精品 | 亚洲国产欧美日韩精品一区二区三区 | 精品久久综合1区2区3区激情 | 永久免费观看国产裸体美女 | 人人妻人人澡人人爽人人精品浪潮 | 少妇一晚三次一区二区三区 | 欧美国产亚洲日韩在线二区 | 亚洲国产精品一区二区第一页 | 鲁一鲁av2019在线 | 亚洲gv猛男gv无码男同 | 两性色午夜免费视频 | 丰满人妻一区二区三区免费视频 | 宝宝好涨水快流出来免费视频 | 色五月丁香五月综合五月 | 欧美国产日韩久久mv | 青草视频在线播放 | 亚洲国产精品久久久久久 | 初尝人妻少妇中文字幕 | 东京无码熟妇人妻av在线网址 | 天天摸天天碰天天添 | 人人妻人人澡人人爽欧美一区 | 131美女爱做视频 | 亚洲无人区午夜福利码高清完整版 | 亚洲国产成人av在线观看 | 日本丰满护士爆乳xxxx | 性欧美熟妇videofreesex | 欧美第一黄网免费网站 | 1000部夫妻午夜免费 | 偷窥日本少妇撒尿chinese | 三级4级全黄60分钟 | 亚洲欧洲日本综合aⅴ在线 | 国产内射老熟女aaaa | 婷婷六月久久综合丁香 | 久久久亚洲欧洲日产国码αv | 国产无套内射久久久国产 | 一本久久伊人热热精品中文字幕 | 夜夜高潮次次欢爽av女 | 精品夜夜澡人妻无码av蜜桃 | 亚洲人成影院在线无码按摩店 | 国产成人无码区免费内射一片色欲 | 2020久久香蕉国产线看观看 | 无码国产乱人伦偷精品视频 | 亚洲小说春色综合另类 | 色五月五月丁香亚洲综合网 | 午夜福利电影 | 熟女少妇人妻中文字幕 | 亚洲中文字幕无码一久久区 | 国产激情艳情在线看视频 | 欧美肥老太牲交大战 | 一本久久a久久精品vr综合 | 99精品久久毛片a片 | 精品日本一区二区三区在线观看 | 99久久人妻精品免费二区 | 亚洲综合无码一区二区三区 | 波多野结衣乳巨码无在线观看 | 久久久久成人片免费观看蜜芽 | 免费无码一区二区三区蜜桃大 | 久久久久久久久888 | 欧美精品一区二区精品久久 | 国产精品理论片在线观看 | 97精品人妻一区二区三区香蕉 | 亚洲色欲色欲欲www在线 | 国产成人精品久久亚洲高清不卡 | 国产两女互慰高潮视频在线观看 | 国产猛烈高潮尖叫视频免费 | 日本一卡2卡3卡四卡精品网站 | 欧美老熟妇乱xxxxx | 最新国产麻豆aⅴ精品无码 | 国产亚洲日韩欧美另类第八页 | 色五月丁香五月综合五月 | 人妻无码久久精品人妻 | 亚洲精品午夜国产va久久成人 | 亚洲精品国产第一综合99久久 | 日本www一道久久久免费榴莲 | 狠狠综合久久久久综合网 | 国产精品怡红院永久免费 | 国产精品a成v人在线播放 | 亚洲男人av天堂午夜在 | 国产精品第一国产精品 | 丰满少妇熟乱xxxxx视频 | 水蜜桃色314在线观看 | 麻豆av传媒蜜桃天美传媒 | 欧美性生交xxxxx久久久 | 国产色精品久久人妻 | 小鲜肉自慰网站xnxx | 成人一在线视频日韩国产 | 国产亚洲tv在线观看 | 99久久久无码国产aaa精品 | 亚洲中文字幕av在天堂 | 无码纯肉视频在线观看 | 伊人久久大香线蕉午夜 | √天堂中文官网8在线 | 国产尤物精品视频 | 人人妻人人澡人人爽人人精品浪潮 | 波多野结衣av一区二区全免费观看 | 亚洲午夜久久久影院 | 久久久久久亚洲精品a片成人 | 中国女人内谢69xxxxxa片 | 亚洲乱亚洲乱妇50p | 欧美国产日韩亚洲中文 | 国产亚洲欧美在线专区 | 色婷婷久久一区二区三区麻豆 | 亚洲国产精品久久人人爱 | 久在线观看福利视频 | 亚洲欧洲日本综合aⅴ在线 | 又粗又大又硬又长又爽 | 久久人妻内射无码一区三区 | 99精品国产综合久久久久五月天 | 麻豆md0077饥渴少妇 | 一个人免费观看的www视频 | 色老头在线一区二区三区 | 丰满护士巨好爽好大乳 | 国产情侣作爱视频免费观看 | 久久99精品国产麻豆 | 精品无码一区二区三区爱欲 | 中文字幕乱码中文乱码51精品 | 啦啦啦www在线观看免费视频 | 欧美xxxx黑人又粗又长 | 伊人色综合久久天天小片 | 51国偷自产一区二区三区 | 无套内谢的新婚少妇国语播放 | 欧美性黑人极品hd | 一本久道久久综合婷婷五月 | av无码电影一区二区三区 | 亚洲成a人一区二区三区 | 欧美日本精品一区二区三区 | 女人和拘做爰正片视频 | 亚洲精品一区二区三区在线 | 成人无码影片精品久久久 | 亚洲aⅴ无码成人网站国产app | 樱花草在线社区www | 国产成人人人97超碰超爽8 | 国产三级久久久精品麻豆三级 | 九九热爱视频精品 | 久久久精品国产sm最大网站 | 欧美zoozzooz性欧美 | 国产精品无码一区二区三区不卡 | 久久综合激激的五月天 | 天天爽夜夜爽夜夜爽 | 欧美人与物videos另类 | 久久天天躁狠狠躁夜夜免费观看 | 国产精品-区区久久久狼 | 51国偷自产一区二区三区 | 国产特级毛片aaaaaaa高清 | 国产一区二区三区四区五区加勒比 | 综合网日日天干夜夜久久 | 国产一区二区三区日韩精品 | 激情爆乳一区二区三区 | 一本一道久久综合久久 | 国产精品亚洲一区二区三区喷水 | 欧美freesex黑人又粗又大 | 97色伦图片97综合影院 | 中文字幕无线码免费人妻 | 亚洲一区二区三区在线观看网站 | 骚片av蜜桃精品一区 | 丰满少妇高潮惨叫视频 | 亚洲精品中文字幕乱码 | 天堂亚洲2017在线观看 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 国产亚av手机在线观看 | 18精品久久久无码午夜福利 | 成人无码精品1区2区3区免费看 | 亚洲欧美国产精品专区久久 | 97se亚洲精品一区 | 亚洲成av人影院在线观看 | 日本丰满护士爆乳xxxx | 噜噜噜亚洲色成人网站 | 免费无码午夜福利片69 | aⅴ亚洲 日韩 色 图网站 播放 | 成人欧美一区二区三区黑人 | 亚洲人亚洲人成电影网站色 | 免费网站看v片在线18禁无码 | 国产精品久久久久影院嫩草 | 少女韩国电视剧在线观看完整 | 呦交小u女精品视频 | 日韩精品久久久肉伦网站 | 无码毛片视频一区二区本码 | 少妇性俱乐部纵欲狂欢电影 | 少妇性荡欲午夜性开放视频剧场 | 蜜桃臀无码内射一区二区三区 | 精品国产青草久久久久福利 | 少妇性l交大片 | 久久精品国产精品国产精品污 | 伊在人天堂亚洲香蕉精品区 | 狠狠色噜噜狠狠狠7777奇米 | 国产又爽又黄又刺激的视频 | 99久久婷婷国产综合精品青草免费 | 狂野欧美性猛xxxx乱大交 | 搡女人真爽免费视频大全 | 国产69精品久久久久app下载 | 久青草影院在线观看国产 | 色婷婷久久一区二区三区麻豆 | 天天做天天爱天天爽综合网 | 精品国产麻豆免费人成网站 | 精品无码av一区二区三区 | 亚洲人亚洲人成电影网站色 | 亚洲中文字幕成人无码 | 国产激情精品一区二区三区 | 欧美日本精品一区二区三区 | 国产精品久久久久影院嫩草 | 欧美激情综合亚洲一二区 | 亚洲精品综合五月久久小说 | 又粗又大又硬毛片免费看 | 内射欧美老妇wbb | 亚洲高清偷拍一区二区三区 | 老子影院午夜精品无码 | 无码福利日韩神码福利片 | 曰韩无码二三区中文字幕 | 四十如虎的丰满熟妇啪啪 | 国产综合久久久久鬼色 | 欧美三级a做爰在线观看 | 99久久精品无码一区二区毛片 | 中文字幕乱码人妻二区三区 | 亚洲爆乳无码专区 | 日本精品久久久久中文字幕 | 日本免费一区二区三区最新 | 欧美阿v高清资源不卡在线播放 | 人妻少妇精品无码专区二区 | 麻花豆传媒剧国产免费mv在线 | 精品无码成人片一区二区98 | 国产成人无码一二三区视频 | 亚洲va欧美va天堂v国产综合 | 国产人妻精品一区二区三区不卡 | 国产精华av午夜在线观看 | 性欧美熟妇videofreesex | 国产精品久久精品三级 | 成人精品一区二区三区中文字幕 | 黑人粗大猛烈进出高潮视频 | 亚洲の无码国产の无码步美 | 亚洲va中文字幕无码久久不卡 | 久久五月精品中文字幕 | 国产三级精品三级男人的天堂 | 男女性色大片免费网站 | 高清不卡一区二区三区 | 亚洲欧美日韩成人高清在线一区 | 亚洲精品国产精品乱码不卡 | 国产热a欧美热a在线视频 | 国产熟女一区二区三区四区五区 | 国产精品人人妻人人爽 | 丰满少妇熟乱xxxxx视频 | 亚洲s码欧洲m码国产av | 国产在线精品一区二区三区直播 | 国产精品久久久久无码av色戒 | 亚洲最大成人网站 | 欧美日韩亚洲国产精品 | 欧美日韩一区二区三区自拍 | 熟妇激情内射com | 高潮毛片无遮挡高清免费 | 精品一区二区三区波多野结衣 | 久久久精品国产sm最大网站 | 黑森林福利视频导航 | 永久黄网站色视频免费直播 | 亚洲欧美国产精品久久 | 玩弄中年熟妇正在播放 | 成人精品一区二区三区中文字幕 | 波多野结衣高清一区二区三区 | 黑人大群体交免费视频 | 性欧美牲交xxxxx视频 | 国产成人一区二区三区别 | 中国女人内谢69xxxx | av香港经典三级级 在线 | 国产精品久久久av久久久 | 99久久人妻精品免费一区 | 中文无码精品a∨在线观看不卡 | 欧美成人午夜精品久久久 | 亚洲爆乳大丰满无码专区 | 日本高清一区免费中文视频 | 午夜福利试看120秒体验区 | 国内综合精品午夜久久资源 | 妺妺窝人体色www在线小说 | 国产两女互慰高潮视频在线观看 | 国产精品久久久久无码av色戒 | 亚洲а∨天堂久久精品2021 | a在线观看免费网站大全 | 在线视频网站www色 | 亚洲成av人片在线观看无码不卡 | www成人国产高清内射 | 麻豆蜜桃av蜜臀av色欲av | 成年美女黄网站色大免费全看 | 中文字幕无线码 | 网友自拍区视频精品 | 18禁黄网站男男禁片免费观看 | 亚洲 a v无 码免 费 成 人 a v | 国产后入清纯学生妹 | 成人性做爰aaa片免费看不忠 | 亚洲の无码国产の无码步美 | 成人精品天堂一区二区三区 | 亚洲高清偷拍一区二区三区 | 欧美一区二区三区视频在线观看 | 一二三四在线观看免费视频 | 久久久久久亚洲精品a片成人 | 欧美精品免费观看二区 | 国产成人无码a区在线观看视频app | 大屁股大乳丰满人妻 | 性做久久久久久久免费看 | 亚洲国产成人av在线观看 | 国产午夜福利100集发布 | 久久久久国色av免费观看性色 | 动漫av网站免费观看 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 清纯唯美经典一区二区 | 国产成人精品久久亚洲高清不卡 | 亚洲一区二区三区香蕉 | 日日天干夜夜狠狠爱 | 国产一区二区不卡老阿姨 | 亚洲小说图区综合在线 | 中国女人内谢69xxxxxa片 | 亚洲午夜福利在线观看 | 久久www免费人成人片 | 青春草在线视频免费观看 | 精品亚洲成av人在线观看 | 无码精品国产va在线观看dvd | 少妇人妻av毛片在线看 | 久久www免费人成人片 | 青青草原综合久久大伊人精品 | 国内精品久久毛片一区二区 | 中文字幕人成乱码熟女app | 亚洲成熟女人毛毛耸耸多 | 日韩精品久久久肉伦网站 | 无套内谢的新婚少妇国语播放 | 亚洲欧洲日本综合aⅴ在线 | 四虎影视成人永久免费观看视频 | 天堂а√在线地址中文在线 | 国产在线一区二区三区四区五区 | 无码纯肉视频在线观看 | 国产高清av在线播放 | 999久久久国产精品消防器材 | 久久人人97超碰a片精品 | 人人爽人人澡人人人妻 | 欧美精品国产综合久久 | 亚洲理论电影在线观看 | 夜夜影院未满十八勿进 | 精品国产青草久久久久福利 | 九一九色国产 | 又湿又紧又大又爽a视频国产 | 狠狠色欧美亚洲狠狠色www | 国产偷自视频区视频 | 国产亚洲欧美日韩亚洲中文色 | 精品国产一区二区三区四区在线看 | 国产成人精品久久亚洲高清不卡 | 久久精品女人天堂av免费观看 | 亚洲 a v无 码免 费 成 人 a v | 亚洲经典千人经典日产 | 日日夜夜撸啊撸 | 丰满岳乱妇在线观看中字无码 | 亚洲成av人影院在线观看 | 亚洲大尺度无码无码专区 | 老熟妇乱子伦牲交视频 | 亚洲成av人片在线观看无码不卡 | 色综合久久网 | 国产精品人妻一区二区三区四 | 日韩精品乱码av一区二区 | 又粗又大又硬又长又爽 | 精品无码国产一区二区三区av | 久久综合色之久久综合 | 97色伦图片97综合影院 | 久久综合九色综合97网 | 久青草影院在线观看国产 | 中文字幕av日韩精品一区二区 | 日本精品人妻无码免费大全 | 精品国产麻豆免费人成网站 | 久久亚洲中文字幕无码 | 人人澡人摸人人添 | 精品国产一区二区三区四区在线看 | 日本熟妇乱子伦xxxx | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 大肉大捧一进一出好爽视频 | 无码av免费一区二区三区试看 | 亚洲欧美日韩国产精品一区二区 | 少妇无码吹潮 | 中国女人内谢69xxxxxa片 | 女人被男人爽到呻吟的视频 | 男人扒开女人内裤强吻桶进去 | av人摸人人人澡人人超碰下载 | 日韩亚洲欧美精品综合 | 草草网站影院白丝内射 | 九九久久精品国产免费看小说 | 精品无码国产一区二区三区av | 99在线 | 亚洲 | 欧洲欧美人成视频在线 | 久久精品女人天堂av免费观看 | 日本一区二区更新不卡 | 国产精品18久久久久久麻辣 | 亚洲一区av无码专区在线观看 | 国内精品人妻无码久久久影院蜜桃 | 丰满护士巨好爽好大乳 | 精品无码一区二区三区的天堂 | 国产人妻久久精品二区三区老狼 | 国产sm调教视频在线观看 | 欧美xxxx黑人又粗又长 | 欧美freesex黑人又粗又大 | 帮老师解开蕾丝奶罩吸乳网站 | 天堂亚洲2017在线观看 | 少妇高潮喷潮久久久影院 | 亚洲中文字幕va福利 | 一本大道久久东京热无码av | 婷婷综合久久中文字幕蜜桃三电影 | 99麻豆久久久国产精品免费 | 色妞www精品免费视频 | 男人的天堂av网站 | 97久久精品无码一区二区 | 国精产品一品二品国精品69xx | 初尝人妻少妇中文字幕 | 久久久久人妻一区精品色欧美 | 日产国产精品亚洲系列 | 久久99久久99精品中文字幕 | 无码一区二区三区在线 | 国内综合精品午夜久久资源 | 两性色午夜免费视频 | 国产精品无码成人午夜电影 | 欧美怡红院免费全部视频 | 欧美激情综合亚洲一二区 | 国产午夜福利亚洲第一 | 少妇性荡欲午夜性开放视频剧场 | 男女猛烈xx00免费视频试看 | 国语自产偷拍精品视频偷 | 久久久中文久久久无码 | a片在线免费观看 | 内射后入在线观看一区 | 亚洲人成网站色7799 | 夜夜躁日日躁狠狠久久av | 国产精品美女久久久网av | 日日麻批免费40分钟无码 | 国产日产欧产精品精品app | 国产女主播喷水视频在线观看 | 六月丁香婷婷色狠狠久久 | 中国女人内谢69xxxx | 亚洲 a v无 码免 费 成 人 a v | 国产精品va在线观看无码 | 日韩精品一区二区av在线 | 天堂一区人妻无码 | 熟妇人妻无乱码中文字幕 | 天天爽夜夜爽夜夜爽 | 国产人妻人伦精品1国产丝袜 | 内射欧美老妇wbb | 精品亚洲韩国一区二区三区 | 国产黄在线观看免费观看不卡 | 在线精品亚洲一区二区 | 伊人久久大香线蕉亚洲 | 美女毛片一区二区三区四区 | 1000部啪啪未满十八勿入下载 | 国产97人人超碰caoprom | 99久久婷婷国产综合精品青草免费 | 亚洲综合伊人久久大杳蕉 | 免费看少妇作爱视频 | 亚洲一区二区三区国产精华液 | 亚洲人成网站在线播放942 | 美女毛片一区二区三区四区 | 免费播放一区二区三区 | 强辱丰满人妻hd中文字幕 | 亚洲の无码国产の无码影院 | 中文字幕 人妻熟女 | 亚洲中文字幕va福利 | 国产农村乱对白刺激视频 | 久久久久av无码免费网 | 久久 国产 尿 小便 嘘嘘 | 人妻中文无码久热丝袜 | 国产电影无码午夜在线播放 | 久在线观看福利视频 | 亚洲中文字幕成人无码 | 久久国产精品精品国产色婷婷 | 精品无人国产偷自产在线 | 一本久道高清无码视频 | 日韩欧美中文字幕公布 | 无码人妻久久一区二区三区不卡 | yw尤物av无码国产在线观看 | 久激情内射婷内射蜜桃人妖 | 免费乱码人妻系列无码专区 | 少妇人妻av毛片在线看 | 久久精品国产99久久6动漫 | 国产另类ts人妖一区二区 | 午夜丰满少妇性开放视频 | 国产 精品 自在自线 | 精品亚洲韩国一区二区三区 | 亚洲色偷偷偷综合网 | www国产精品内射老师 | aⅴ在线视频男人的天堂 | 国产莉萝无码av在线播放 | 色综合久久久无码中文字幕 | 亚洲国产精品一区二区美利坚 | 两性色午夜视频免费播放 | 日韩欧美中文字幕公布 | 精品乱码久久久久久久 | 亚洲欧美精品aaaaaa片 | 国产精品鲁鲁鲁 | 国产精品久久久久7777 | 久久久久av无码免费网 | 天下第一社区视频www日本 | 亚洲乱码日产精品bd | 国产精品嫩草久久久久 | 午夜精品久久久久久久久 | 国精产品一品二品国精品69xx | 免费观看激色视频网站 | 国产真实乱对白精彩久久 | 无码帝国www无码专区色综合 | 无码人妻出轨黑人中文字幕 | 夜夜躁日日躁狠狠久久av | 国产成人人人97超碰超爽8 | 午夜性刺激在线视频免费 | 中文字幕 人妻熟女 | 久久伊人色av天堂九九小黄鸭 | 欧美精品免费观看二区 | 午夜福利试看120秒体验区 | 夜精品a片一区二区三区无码白浆 | 亚无码乱人伦一区二区 | 麻豆av传媒蜜桃天美传媒 | 欧美午夜特黄aaaaaa片 | 亚洲精品成人福利网站 | 国产免费久久久久久无码 | 国产超级va在线观看视频 | 国产性生大片免费观看性 | 久久精品国产亚洲精品 | 又大又紧又粉嫩18p少妇 | 丰满少妇熟乱xxxxx视频 | 久久久亚洲欧洲日产国码αv | 亚洲成a人片在线观看无码 | 99国产精品白浆在线观看免费 | 日韩亚洲欧美精品综合 | 欧美性生交xxxxx久久久 | 无码人妻av免费一区二区三区 | 性生交大片免费看l | 国产精品亚洲专区无码不卡 | 久热国产vs视频在线观看 | 一个人看的www免费视频在线观看 | 久久99国产综合精品 | 中国女人内谢69xxxx | 亚洲精品欧美二区三区中文字幕 | 久久久久久国产精品无码下载 | 台湾无码一区二区 | 牛和人交xxxx欧美 | 午夜性刺激在线视频免费 | 久久亚洲国产成人精品性色 | 欧美刺激性大交 | 国产人妻精品一区二区三区 | 国产精品美女久久久久av爽李琼 | 一二三四社区在线中文视频 | 中文无码伦av中文字幕 | 久久99精品久久久久婷婷 | 婷婷丁香五月天综合东京热 | 午夜熟女插插xx免费视频 | 国产精品毛多多水多 | 成人性做爰aaa片免费看 | 国色天香社区在线视频 | 欧美第一黄网免费网站 | 青春草在线视频免费观看 | 又大又紧又粉嫩18p少妇 | 久久人人爽人人人人片 | 亚洲乱码国产乱码精品精 | 高潮毛片无遮挡高清免费 | 色婷婷香蕉在线一区二区 | 国产精品久久久久久亚洲毛片 | 99精品视频在线观看免费 | 日韩精品a片一区二区三区妖精 | 久久久久国色av免费观看性色 | 国产激情无码一区二区app | 亚洲大尺度无码无码专区 | 日日麻批免费40分钟无码 | 午夜精品一区二区三区的区别 | 欧洲熟妇色 欧美 | 无码任你躁久久久久久久 | 中文字幕人成乱码熟女app | 日本va欧美va欧美va精品 | 国产在线精品一区二区高清不卡 | 久久久久免费精品国产 | 99久久人妻精品免费二区 | 亚洲日韩精品欧美一区二区 | 粉嫩少妇内射浓精videos | 欧美一区二区三区视频在线观看 | 精品久久久无码人妻字幂 | 欧美成人家庭影院 | 精品人妻av区 | 丝袜足控一区二区三区 | 又大又紧又粉嫩18p少妇 | 久久久久久久久蜜桃 | 老子影院午夜伦不卡 | 免费国产成人高清在线观看网站 | 天天av天天av天天透 | 亚洲精品鲁一鲁一区二区三区 | 久久久精品456亚洲影院 | 国产精品视频免费播放 | 亚洲国产精品一区二区美利坚 | 老熟妇仑乱视频一区二区 | 欧美成人午夜精品久久久 | 波多野结衣aⅴ在线 | 人人澡人摸人人添 | 亚洲aⅴ无码成人网站国产app | 黑人巨大精品欧美黑寡妇 | 日韩亚洲欧美精品综合 | 熟妇人妻中文av无码 | 久久久无码中文字幕久... | 日本高清一区免费中文视频 | 亚洲精品美女久久久久久久 | 欧美人妻一区二区三区 | 又色又爽又黄的美女裸体网站 | 国产亚洲欧美日韩亚洲中文色 | 国产精品无码久久av | 久久精品国产亚洲精品 | 大肉大捧一进一出好爽视频 | 丰满少妇熟乱xxxxx视频 | 又大又硬又爽免费视频 | 精品少妇爆乳无码av无码专区 | 精品一二三区久久aaa片 | 精品久久久无码人妻字幂 | 亚无码乱人伦一区二区 | 国产亚洲日韩欧美另类第八页 | 人人妻人人澡人人爽人人精品浪潮 | 成人一区二区免费视频 | 国产真实乱对白精彩久久 | 六十路熟妇乱子伦 | 日本一区二区三区免费高清 | 免费无码肉片在线观看 | 精品国产一区二区三区av 性色 | 国产高清不卡无码视频 | 国产福利视频一区二区 | 99久久亚洲精品无码毛片 | 清纯唯美经典一区二区 | 影音先锋中文字幕无码 | 香蕉久久久久久av成人 | 色五月五月丁香亚洲综合网 | 奇米影视7777久久精品 | 未满小14洗澡无码视频网站 | 午夜福利一区二区三区在线观看 | 亚洲爆乳精品无码一区二区三区 | 三级4级全黄60分钟 | 无码人妻黑人中文字幕 | 日日噜噜噜噜夜夜爽亚洲精品 | 99久久精品午夜一区二区 | 99久久精品无码一区二区毛片 | 国产av人人夜夜澡人人爽麻豆 | 中文字幕中文有码在线 | 精品欧美一区二区三区久久久 | 中文字幕乱码中文乱码51精品 | 亚洲人成网站免费播放 | 亚洲精品综合五月久久小说 | 中文字幕+乱码+中文字幕一区 | 中文字幕av日韩精品一区二区 | 国内综合精品午夜久久资源 | 中文字幕人妻无码一区二区三区 | 日本在线高清不卡免费播放 | 中文字幕无码日韩专区 | 激情内射亚州一区二区三区爱妻 | 鲁鲁鲁爽爽爽在线视频观看 | 特级做a爰片毛片免费69 | 国产黄在线观看免费观看不卡 | 成人av无码一区二区三区 | 久久综合狠狠综合久久综合88 | 久久综合色之久久综合 | 亚洲人成无码网www | 奇米影视7777久久精品 | 无码人妻精品一区二区三区下载 | 东北女人啪啪对白 | 国产一区二区三区精品视频 | 成 人影片 免费观看 | 国产亚av手机在线观看 | 一个人看的www免费视频在线观看 | 亚洲精品一区三区三区在线观看 | 牛和人交xxxx欧美 | 国产人妻人伦精品 | 婷婷丁香六月激情综合啪 | 夜先锋av资源网站 | 影音先锋中文字幕无码 | 领导边摸边吃奶边做爽在线观看 | 久久综合网欧美色妞网 | 国产一区二区三区精品视频 | 日本va欧美va欧美va精品 | 亚拍精品一区二区三区探花 | 在线播放免费人成毛片乱码 | 欧美阿v高清资源不卡在线播放 | 日韩少妇内射免费播放 | 少妇无码吹潮 | 国产乱子伦视频在线播放 | 久久久久久久女国产乱让韩 | 精品无码一区二区三区爱欲 | 人人澡人摸人人添 | 免费网站看v片在线18禁无码 | 午夜精品久久久内射近拍高清 | 夜精品a片一区二区三区无码白浆 | 中文字幕人妻无码一夲道 | 精品一区二区三区波多野结衣 | 又黄又爽又色的视频 | 精品久久久久久人妻无码中文字幕 | 图片小说视频一区二区 | 成人三级无码视频在线观看 | 色一情一乱一伦一视频免费看 | 国产精品第一国产精品 | 色噜噜亚洲男人的天堂 | 精品一区二区三区波多野结衣 | 亚洲综合无码久久精品综合 | 欧美大屁股xxxxhd黑色 | 97资源共享在线视频 | 久久精品无码一区二区三区 | 国产欧美亚洲精品a | 亚洲熟女一区二区三区 | 扒开双腿吃奶呻吟做受视频 | 天天拍夜夜添久久精品大 | 亚洲欧美日韩国产精品一区二区 | 成人无码精品一区二区三区 | 欧美性猛交xxxx富婆 |