Android編程實(shí)現(xiàn)的超炫圖片瀏覽器
本文實(shí)例講述了Android編程實(shí)現(xiàn)的超炫圖片瀏覽器。分享給大家供大家參考,具體如下:
使用過Android自帶的gallery組件的人都知道,gallery實(shí)現(xiàn)的效果就是拖動(dòng)瀏覽一組圖片,相比iphone里也是用于拖動(dòng)瀏覽圖片的coverflow,顯然遜色不少。實(shí)際上,可以通過擴(kuò)展gallery,通過偽3D變換可以基本實(shí)現(xiàn)coverflow的效果。本文通過源代碼解析這一功能的實(shí)現(xiàn)。具體代碼作用可參照注釋。
最終實(shí)現(xiàn)效果如下:
要使用gallery,我們必須首先給其指定一個(gè)adapter。在這里,我們實(shí)現(xiàn)了一個(gè)自定義的ImageAdapter,為圖片制作倒影效果。
傳入?yún)?shù)為context和程序內(nèi)drawable中的圖片ID數(shù)組。之后調(diào)用其中的createReflectedImages()方法分別創(chuàng)造每一個(gè)圖像的倒影效果,生成對(duì)應(yīng)的ImageView數(shù)組,最后在getView()中返回。
Copyright (C) 2010 Neil Davies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * [url]http://www.apache.org/licenses/LICENSE-2.0[/url] * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * This code is base on the Android Gallery widget and was Created * by Neil Davies neild001 'at' gmail dot com to be a Coverflow widget * * @author Neil Davies */ public class ImageAdapter extends BaseAdapter { int mGalleryItemBackground; private Context mContext; private Integer[] mImageIds ; private ImageView[] mImages; public ImageAdapter(Context c, int[] ImageIds) { mContext = c; mImageIds = ImageIds; mImages = new ImageView[mImageIds.length]; } public boolean createReflectedImages() { // The gap we want between the reflection and the original image final int reflectionGap = 4; int index = 0; for (int imageId : mImageIds) { Bitmap originalImage = BitmapFactory.decodeResource( mContext.getResources(), imageId); int width = originalImage.getWidth(); int height = originalImage.getHeight(); // This will not scale but will flip on the Y axis Matrix matrix = new Matrix(); matrix.preScale(1, -1); // Create a Bitmap with the flip matrix applied to it. // We only want the bottom half of the image Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, height / 2, width, height / 2, matrix, false); // Create a new bitmap with same width but taller to fit // reflection Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + height / 2), Config.ARGB_8888); // Create a new Canvas with the bitmap that's big enough for // the image plus gap plus reflection Canvas canvas = new Canvas(bitmapWithReflection); // Draw in the original image canvas.drawBitmap(originalImage, 0, 0, null); // Draw in the gap Paint deafaultPaint = new Paint(); canvas.drawRect(0, height, width, height + reflectionGap, deafaultPaint); // Draw in the reflection canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null); // Create a shader that is a linear gradient that covers the // reflection Paint paint = new Paint(); LinearGradient shader = new LinearGradient(0, originalImage.getHeight(), 0, bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff, 0x00ffffff, TileMode.CLAMP); // Set the paint to use this shader (linear gradient) paint.setShader(shader); // Set the Transfer mode to be porter duff and destination in paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); // Draw a rectangle using the paint with our linear gradient canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + reflectionGap, paint); ImageView imageView = new ImageView(mContext); imageView.setImageBitmap(bitmapWithReflection); imageView .setLayoutParams(new GalleryFlow.LayoutParams(160, 240)); // imageView.setScaleType(ScaleType.MATRIX); mImages[index++] = imageView; } return true; } public int getCount() { return mImageIds.length; } public Object getItem(int position) { return position; } public long getItemId(int position) { return position; } public View getView(int position, View convertView, ViewGroup parent) { // Use this code if you want to load from resources /* * ImageView i = new ImageView(mContext); * i.setImageResource(mImageIds[position]); i.setLayoutParams(new * CoverFlow.LayoutParams(350,350)); * i.setScaleType(ImageView.ScaleType.CENTER_INSIDE); * * //Make sure we set anti-aliasing otherwise we get jaggies * BitmapDrawable drawable = (BitmapDrawable) i.getDrawable(); * drawable.setAntiAlias(true); return i; */ return mImages[position]; } /** * Returns the size (0.0f to 1.0f) of the views depending on the * 'offset' to the center. */ public float getScale(boolean focused, int offset) { /* Formula: 1 / (2 ^ offset) */ returnMath.max(0,1.0f / (float) Math.pow(2, Math.abs(offset))); } } }
僅僅實(shí)現(xiàn)了圖片的倒影效果還不夠,因?yàn)樵赾overflow中圖片切換是有旋轉(zhuǎn)和縮放效果的,而自帶的gallery中并沒有實(shí)現(xiàn)。因此,我們擴(kuò)展自帶的gallery,實(shí)現(xiàn)自己的galleryflow。在原gallery類中,提供了一個(gè)方法getChildStaticTransformation()以實(shí)現(xiàn)對(duì)圖片的變換。我們通過覆寫這個(gè)方法并在其中調(diào)用自定義的transformImageBitmap(“每個(gè)圖片與gallery中心的距離”)方法,,即可實(shí)現(xiàn)每個(gè)圖片做相應(yīng)的旋轉(zhuǎn)和縮放。其中使用了camera和matrix用于視圖變換。具體可參考代碼注釋。
public class GalleryFlow extendsGallery { /** * Graphics Camera used for transforming the matrix of ImageViews */ privateCamera mCamera = newCamera(); /** * The maximum angle the Child ImageView will be rotated by */ privateint mMaxRotationAngle =60; /** * The maximum zoom on the centre Child */ privateint mMaxZoom = -120; /** * The Centre of the Coverflow */ privateint mCoveflowCenter; publicGalleryFlow(Context context) { super(context); this.setStaticTransformationsEnabled(true); } publicGalleryFlow(Context context, AttributeSet attrs) { super(context, attrs); this.setStaticTransformationsEnabled(true); } publicGalleryFlow(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.setStaticTransformationsEnabled(true); } /** * Get the max rotational angle of the image * * @return the mMaxRotationAngle */ publicint getMaxRotationAngle() { returnmMaxRotationAngle; } /** * Set the max rotational angle of each image * * @param maxRotationAngle * the mMaxRotationAngle to set */ publicvoid setMaxRotationAngle(intmaxRotationAngle) { mMaxRotationAngle = maxRotationAngle; } /** * Get the Max zoom of the centre image * * @return the mMaxZoom */ publicint getMaxZoom() { returnmMaxZoom; } /** * Set the max zoom of the centre image * * @param maxZoom * the mMaxZoom to set */ publicvoid setMaxZoom(intmaxZoom) { mMaxZoom = maxZoom; } /** * Get the Centre of the Coverflow * * @return The centre of this Coverflow. */ privateint getCenterOfCoverflow() { return(getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft(); } /** * Get the Centre of the View * * @return The centre of the given view. */ privatestatic int getCenterOfView(View view) { returnview.getLeft() + view.getWidth() / 2; } /** * {@inheritDoc} * * @see #setStaticTransformationsEnabled(boolean) */ protectedboolean getChildStaticTransformation(View child, Transformation t) { finalint childCenter = getCenterOfView(child); finalint childWidth = child.getWidth(); introtationAngle = 0; t.clear(); t.setTransformationType(Transformation.TYPE_MATRIX); if(childCenter == mCoveflowCenter) { transformImageBitmap((ImageView) child, t,0); }else { rotationAngle = (int) (((float) (mCoveflowCenter - childCenter) / childWidth) * mMaxRotationAngle); if(Math.abs(rotationAngle) > mMaxRotationAngle) { rotationAngle = (rotationAngle <0) ? -mMaxRotationAngle : mMaxRotationAngle; } transformImageBitmap((ImageView) child, t, rotationAngle); } returntrue; } /** * This is called during layout when the size of this view has changed. If * you were just added to the view hierarchy, you're called with the old * values of 0. * * @param w * Current width of this view. * @param h * Current height of this view. * @param oldw * Old width of this view. * @param oldh * Old height of this view. */ protectedvoid onSizeChanged(intw, int h, int oldw, int oldh) { mCoveflowCenter = getCenterOfCoverflow(); super.onSizeChanged(w, h, oldw, oldh); } /** * Transform the Image Bitmap by the Angle passed * * @param imageView * ImageView the ImageView whose bitmap we want to rotate * @param t * transformation * @param rotationAngle * the Angle by which to rotate the Bitmap */ privatevoid transformImageBitmap(ImageView child, Transformation t, introtationAngle) { mCamera.save(); finalMatrix imageMatrix = t.getMatrix(); finalint imageHeight = child.getLayoutParams().height; finalint imageWidth = child.getLayoutParams().width; finalint rotation = Math.abs(rotationAngle); // 在Z軸上正向移動(dòng)camera的視角,實(shí)際效果為放大圖片。 // 如果在Y軸上移動(dòng),則圖片上下移動(dòng);X軸上對(duì)應(yīng)圖片左右移動(dòng)。 mCamera.translate(0.0f,0.0f, 100.0f); // As the angle of the view gets less, zoom in if(rotation < mMaxRotationAngle) { floatzoomAmount = (float) (mMaxZoom + (rotation *1.5)); mCamera.translate(0.0f,0.0f, zoomAmount); } // 在Y軸上旋轉(zhuǎn),對(duì)應(yīng)圖片豎向向里翻轉(zhuǎn)。 // 如果在X軸上旋轉(zhuǎn),則對(duì)應(yīng)圖片橫向向里翻轉(zhuǎn)。 mCamera.rotateY(rotationAngle); mCamera.getMatrix(imageMatrix); imageMatrix.preTranslate(-(imageWidth /2), -(imageHeight /2)); imageMatrix.postTranslate((imageWidth /2), (imageHeight /2)); mCamera.restore(); } }
代碼到這里就結(jié)束了。有興趣的話可以自行調(diào)整里面的參數(shù)來實(shí)現(xiàn)更多更炫的效果。
下面是調(diào)用的示例:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_gallery); Integer[] images = { R.drawable.img0001, R.drawable.img0030, R.drawable.img0100, R.drawable.img0130, R.drawable.img0200, R.drawable.img0230, R.drawable.img0300, R.drawable.img0330, R.drawable.img0354 }; ImageAdapter adapter =new ImageAdapter(this, images); adapter.createReflectedImages(); GalleryFlow galleryFlow = (GalleryFlow) findViewById(R.id.gallery_flow); galleryFlow.setAdapter(adapter); }
PS1:
可以看出來這樣實(shí)現(xiàn)的gallery鋸齒問題比較嚴(yán)重??梢栽赾reateReflectedImages()使用以下代碼:
BitmapDrawable bd = new BitmapDrawable(bitmapWithReflection); bd.setAntiAlias(true);
然后用iv.setImageDrawable(bd);
代替iv.setImageBitmap(bitmapWithReflection);
即可基本消除鋸齒。
PS2:
ImageAdapter有待確定的MemoryLeak問題,貌似的Bitmap的decode方法會(huì)造成ML,使用ImageAdapter時(shí)多次旋轉(zhuǎn)屏幕后會(huì)出現(xiàn)OOM。目前可以通過將使用完畢的bimap調(diào)用recycle()方法和設(shè)置null并及時(shí)調(diào)用system.gc()得到一些改善,但是問題并不明顯。
慶祝精華和推薦,增加3個(gè)PS~
PS3 ON PS1:
為什么開啟抗鋸齒后不明顯。答案是,鋸齒不可能完全消除,但開啟抗鋸齒后會(huì)有很大改善。
另外還說到為什么android不默認(rèn)開啟鋸齒,以下是我的一點(diǎn)想法:
插值是我現(xiàn)在所知道的抗鋸齒的算法,也就是計(jì)算像素間的相關(guān)度對(duì)其間插入中間像素以達(dá)到平滑圖像邊緣的效果。但這無疑會(huì)耗費(fèi)了大量的運(yùn)算。
雖然我沒有經(jīng)過測(cè)試,但是我猜測(cè),使用antialias后圖形性能至少會(huì)下降30%。
當(dāng)然,在這里沒有涉及到復(fù)雜的圖形運(yùn)算,所以開啟抗鋸齒不會(huì)有很明顯的性能影響,但如果你在模擬器或者低端機(jī)型上測(cè)試就會(huì)發(fā)現(xiàn)一點(diǎn)問題。
PS4:
有人問到transformImageBitmap()中這倆句話是什么意思:
imageMatrix.preTranslate(-(imageWidth / 2), -(imageHeight / 2)); imageMatrix.postTranslate((imageWidth / 2), (imageHeight / 2));
個(gè)人的理解如下:
preTranslate相當(dāng)于在對(duì)圖像進(jìn)行任何矩陣變換前先進(jìn)行preTranslate,postTranslate相反,進(jìn)行所有變換后再執(zhí)行postTranlate。
這倆句的意思是:在做任何變換前,先將整個(gè)圖像從圖像的中心點(diǎn)移動(dòng)到原點(diǎn)((0,0)點(diǎn)),執(zhí)行變換完畢后再將圖像從原點(diǎn)移動(dòng)到之前的中心點(diǎn)。
如果不加這倆句,任何變換將以圖像的原點(diǎn)為變換中心點(diǎn),加了之后,任何變換都將以圖像的中心點(diǎn)為變換中心點(diǎn)。
舉個(gè)例子,對(duì)圖像進(jìn)行旋轉(zhuǎn),需要倆個(gè)參數(shù):一個(gè)是旋轉(zhuǎn)的角度,另一個(gè)是旋轉(zhuǎn)中心的坐標(biāo)。旋轉(zhuǎn)中心的坐標(biāo)影響旋轉(zhuǎn)的效果。這個(gè)能明白嗎?你拿一根棍子,拿著棍子的一端進(jìn)行旋轉(zhuǎn)和拿在棍子中間旋轉(zhuǎn),是不一樣的。preTranslate和postTranslate執(zhí)行后對(duì)圖像本身不會(huì)有影響,影響的是對(duì)圖像進(jìn)行變換時(shí)的旋轉(zhuǎn)軸。
說了這么多有點(diǎn)繞,其實(shí)就是矩陣變換的知識(shí)。
PS5 ON PS2:
這個(gè)問題在google group下有過很充分的討論,貌似一般只在debug模式下存在?,F(xiàn)在我使用這段代碼沒有出現(xiàn)OOM問題了
希望本文所述對(duì)大家Android程序設(shè)計(jì)有所幫助。
相關(guān)文章
android設(shè)備不識(shí)別awk命令 缺少busybox怎么辦
這篇文章主要為大家詳細(xì)介紹了android設(shè)備不識(shí)別awk命令,缺少busybox的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04Flutter應(yīng)用集成極光推送的實(shí)現(xiàn)示例
這篇文章主要介紹了Flutter應(yīng)用集成極光推送的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02Android開發(fā)之Adobe flash操作工具類
這篇文章主要介紹了Android開發(fā)之Adobe flash操作工具類,可實(shí)現(xiàn)flash的安裝及判斷flash是否安裝等功能,需要的朋友可以參考下2017-12-12Android RecyclerView選擇多個(gè)item的實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView選擇多個(gè)item的實(shí)現(xiàn)代碼,仿網(wǎng)易新聞客戶端頻道選擇效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android實(shí)現(xiàn)從底部彈出的Dialog示例(一)
這篇文章主要介紹了Android實(shí)現(xiàn)從底部彈出的Dialog示例(一),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-01-01Android 自定義View實(shí)現(xiàn)多節(jié)點(diǎn)進(jìn)度條功能
這篇文章主要介紹了Android 自定義View實(shí)現(xiàn)多節(jié)點(diǎn)進(jìn)度條,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05如何使用Android實(shí)現(xiàn)接口實(shí)信息在留言板顯示
這篇文章主要介紹了如何使用Android接口實(shí)現(xiàn)信息的留言板顯示,需要的朋友可以參考下2015-07-07Android 自定義ListView實(shí)現(xiàn)QQ空間界面(說說內(nèi)包含圖片、視頻、點(diǎn)贊、評(píng)論、轉(zhuǎn)發(fā)功能)
這篇文章主要介紹了Android 自定義ListView實(shí)現(xiàn)QQ空間界面,qq空間說說內(nèi)包含圖片、視頻、點(diǎn)贊、評(píng)論、轉(zhuǎn)發(fā)功能,需要的朋友可以參考下2019-12-12Android App開發(fā)中使用RecyclerView實(shí)現(xiàn)Gallery畫廊的實(shí)例
這篇文章主要介紹了Android App開發(fā)中使用RecyclerView實(shí)現(xiàn)Gallery畫廊的實(shí)例,比普通的ListView實(shí)現(xiàn)的效果更為強(qiáng)大,需要的朋友可以參考下2016-04-04