Android瀑布流照片牆實現,體驗不規則排列的美感
轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/10470797
傳統介面的佈局方式總是行列分明、坐落有序的,這種佈局已是司空見慣,在不知不覺中大家都已經對它產生了審美疲勞。這個時候瀑布流佈局的出現,就給人帶來了耳目一新的感覺,這種佈局雖然看上去貌似毫無規律,但是卻有一種說不上來的美感,以至於湧現出了大批的網站和應用紛紛使用這種新穎的佈局來設計介面。
記得我在之前已經寫過一篇關於如何在Android上實現照片牆功能的文章了,但那個時候是使用的GridView來進行佈局的,這種佈局方式只適用於“牆”上的每張圖片大小都相同的情況,如果圖片的大小參差不齊,在GridView中顯示就會非常的難看。而使用瀑布流的佈局方式就可以很好地解決這個問題,因此今天我們也來趕一下潮流,看看如何在Android上實現瀑布流照片牆的功能。
首先還是講一下實現原理,瀑布流的佈局方式雖然看起來好像排列的很隨意,其實它是有很科學的排列規則的。整個介面會根據螢幕的寬度劃分成等寬的若干列,由於手機的螢幕不是很大,這裡我們就分成三列。每當需要新增一張圖片時,會將這張圖片的寬度壓縮成和列一樣寬,再按照同樣的壓縮比例對圖片的高度進行壓縮,然後在這三列中找出當前高度最小的一列,將圖片新增到這一列中。之後每當需要新增一張新圖片時,都去重複上面的操作,就會形成瀑布流格局的照片牆,示意圖如下所示。
聽我這麼說完後,你可能會覺得瀑布流的佈局非常簡單嘛,只需要使用三個LinearLayout平分整個螢幕寬度,然後動態地addView()進去就好了。確實如此,如果只是為了實現功能的話,就是這麼簡單。可是別忘了,我們是在手機上進行開發,如果不停地往LinearLayout裡新增圖片,程式很快就會OOM。因此我們還需要一個合理的方案來對圖片資源進行釋放,這裡仍然是準備使用LruCache演算法,對這個演算法不熟悉的朋友可以先參考Android高效載入大圖、多圖方案,有效避免程式OOM 。
下面我們就來開始實現吧,新建一個Android專案,起名叫PhotoWallFallsDemo,並選擇4.0的API。
第一個要考慮的問題是,我們到哪兒去收集這些大小參差不齊的圖片呢?這裡我事先在百度上搜尋了很多張風景圖片,並且為了保證它們訪問的穩定性,我將這些圖片都上傳到了我的CSDN相簿裡,因此只要從這裡下載圖片就可以了。新建一個Images類,將所有相簿中圖片的網址都配置進去,程式碼如下所示:
- public class Images {
- public final static String[] imageUrls = new String[] {
- "https://img-my.csdn.net/uploads/201309/01/1378037235_3453.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037235_9280.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037234_3539.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037234_6318.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037194_2965.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037193_1687.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037193_1286.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037192_8379.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037178_9374.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037177_1254.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037177_6203.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037152_6352.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037151_9565.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037151_7904.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037148_7104.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037129_8825.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037128_5291.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037128_3531.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037127_1085.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037095_7515.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037094_8001.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037093_7168.jpg",
- "https://img-my.csdn.net/uploads/201309/01/1378037091_4950.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949643_6410.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949642_6939.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949630_4505.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949630_4593.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949629_7309.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949629_8247.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949615_1986.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949614_8482.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949614_3743.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949614_4199.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949599_3416.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949599_5269.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949598_7858.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949598_9982.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949578_2770.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949578_8744.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949577_5210.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949577_1998.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949482_8813.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949481_6577.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949480_4490.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949455_6792.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949455_6345.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949442_4553.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949441_8987.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949441_5454.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949454_6367.jpg",
- "https://img-my.csdn.net/uploads/201308/31/1377949442_4562.jpg" };
- }
- public class ImageLoader {
- /**
- * 圖片快取技術的核心類,用於快取所有下載好的圖片,在程式記憶體達到設定值時會將最少最近使用的圖片移除掉。
- */
- private static LruCache<String, Bitmap> mMemoryCache;
- /**
- * ImageLoader的例項。
- */
- private static ImageLoader mImageLoader;
- private ImageLoader() {
- // 獲取應用程式最大可用記憶體
- int maxMemory = (int) Runtime.getRuntime().maxMemory();
- int cacheSize = maxMemory / 8;
- // 設定圖片快取大小為程式最大可用記憶體的1/8
- mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
- @Override
- protected int sizeOf(String key, Bitmap bitmap) {
- return bitmap.getByteCount();
- }
- };
- }
- /**
- * 獲取ImageLoader的例項。
- *
- * @return ImageLoader的例項。
- */
- public static ImageLoader getInstance() {
- if (mImageLoader == null) {
- mImageLoader = new ImageLoader();
- }
- return mImageLoader;
- }
- /**
- * 將一張圖片儲存到LruCache中。
- *
- * @param key
- * LruCache的鍵,這裡傳入圖片的URL地址。
- * @param bitmap
- * LruCache的鍵,這裡傳入從網路上下載的Bitmap物件。
- */
- public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
- if (getBitmapFromMemoryCache(key) == null) {
- mMemoryCache.put(key, bitmap);
- }
- }
- /**
- * 從LruCache中獲取一張圖片,如果不存在就返回null。
- *
- * @param key
- * LruCache的鍵,這裡傳入圖片的URL地址。
- * @return 對應傳入鍵的Bitmap物件,或者null。
- */
- public Bitmap getBitmapFromMemoryCache(String key) {
- return mMemoryCache.get(key);
- }
- public static int calculateInSampleSize(BitmapFactory.Options options,
- int reqWidth) {
- // 源圖片的寬度
- final int width = options.outWidth;
- int inSampleSize = 1;
- if (width > reqWidth) {
- // 計算出實際寬度和目標寬度的比率
- final int widthRatio = Math.round((float) width / (float) reqWidth);
- inSampleSize = widthRatio;
- }
- return inSampleSize;
- }
- public static Bitmap decodeSampledBitmapFromResource(String pathName,
- int reqWidth) {
- // 第一次解析將inJustDecodeBounds設定為true,來獲取圖片大小
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(pathName, options);
- // 呼叫上面定義的方法計算inSampleSize值
- options.inSampleSize = calculateInSampleSize(options, reqWidth);
- // 使用獲取到的inSampleSize值再次解析圖片
- options.inJustDecodeBounds = false;
- return BitmapFactory.decodeFile(pathName, options);
- }
- }
接下來新建MyScrollView繼承自ScrollView,程式碼如下所示:
- public class MyScrollView extends ScrollView implements OnTouchListener {
- /**
- * 每頁要載入的圖片數量
- */
- public static final int PAGE_SIZE = 15;
- /**
- * 記錄當前已載入到第幾頁
- */
- private int page;
- /**
- * 每一列的寬度
- */
- private int columnWidth;
- /**
- * 當前第一列的高度
- */
- private int firstColumnHeight;
- /**
- * 當前第二列的高度
- */
- private int secondColumnHeight;
- /**
- * 當前第三列的高度
- */
- private int thirdColumnHeight;
- /**
- * 是否已載入過一次layout,這裡onLayout中的初始化只需載入一次
- */
- private boolean loadOnce;
- /**
- * 對圖片進行管理的工具類
- */
- private ImageLoader imageLoader;
- /**
- * 第一列的佈局
- */
- private LinearLayout firstColumn;
- /**
- * 第二列的佈局
- */
- private LinearLayout secondColumn;
- /**
- * 第三列的佈局
- */
- private LinearLayout thirdColumn;
- /**
- * 記錄所有正在下載或等待下載的任務。
- */
- private static Set<LoadImageTask> taskCollection;
- /**
- * MyScrollView下的直接子佈局。
- */
- private static View scrollLayout;
- /**
- * MyScrollView佈局的高度。
- */
- private static int scrollViewHeight;
- /**
- * 記錄上垂直方向的滾動距離。
- */
- private static int lastScrollY = -1;
- /**
- * 記錄所有介面上的圖片,用以可以隨時控制對圖片的釋放。
- */
- private List<ImageView> imageViewList = new ArrayList<ImageView>();
- /**
- * 在Handler中進行圖片可見性檢查的判斷,以及載入更多圖片的操作。
- */
- private static Handler handler = new Handler() {
- public void handleMessage(android.os.Message msg) {
- MyScrollView myScrollView = (MyScrollView) msg.obj;
- int scrollY = myScrollView.getScrollY();
- // 如果當前的滾動位置和上次相同,表示已停止滾動
- if (scrollY == lastScrollY) {
- // 當滾動的最底部,並且當前沒有正在下載的任務時,開始載入下一頁的圖片
- if (scrollViewHeight + scrollY >= scrollLayout.getHeight()
- && taskCollection.isEmpty()) {
- myScrollView.loadMoreImages();
- }
- myScrollView.checkVisibility();
- } else {
- lastScrollY = scrollY;
- Message message = new Message();
- message.obj = myScrollView;
- // 5毫秒後再次對滾動位置進行判斷
- handler.sendMessageDelayed(message, 5);
- }
- };
- };
- /**
- * MyScrollView的建構函式。
- *
- * @param context
- * @param attrs
- */
- public MyScrollView(Context context, AttributeSet attrs) {
- super(context, attrs);
- imageLoader = ImageLoader.getInstance();
- taskCollection = new HashSet<LoadImageTask>();
- setOnTouchListener(this);
- }
- /**
- * 進行一些關鍵性的初始化操作,獲取MyScrollView的高度,以及得到第一列的寬度值。並在這裡開始載入第一頁的圖片。
- */
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- if (changed && !loadOnce) {
- scrollViewHeight = getHeight();
- scrollLayout = getChildAt(0);
- firstColumn = (LinearLayout) findViewById(R.id.first_column);
- secondColumn = (LinearLayout) findViewById(R.id.second_column);
- thirdColumn = (LinearLayout) findViewById(R.id.third_column);
- columnWidth = firstColumn.getWidth();
- loadOnce = true;
- loadMoreImages();
- }
- }
- /**
- * 監聽使用者的觸屏事件,如果使用者手指離開螢幕則開始進行滾動檢測。
- */
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
- Message message = new Message();
- message.obj = this;
- handler.sendMessageDelayed(message, 5);
- }
- return false;
- }
- /**
- * 開始載入下一頁的圖片,每張圖片都會開啟一個非同步執行緒去下載。
- */
- public void loadMoreImages() {
- if (hasSDCard()) {
- int startIndex = page * PAGE_SIZE;
- int endIndex = page * PAGE_SIZE + PAGE_SIZE;
- if (startIndex < Images.imageUrls.length) {
- Toast.makeText(getContext(), "正在載入...", Toast.LENGTH_SHORT)
- .show();
- if (endIndex > Images.imageUrls.length) {
- endIndex = Images.imageUrls.length;
- }
- for (int i = startIndex; i < endIndex; i++) {
- LoadImageTask task = new LoadImageTask();
- taskCollection.add(task);
- task.execute(Images.imageUrls[i]);
- }
- page++;
- } else {
- Toast.makeText(getContext(), "已沒有更多圖片", Toast.LENGTH_SHORT)
- .show();
- }
- } else {
- Toast.makeText(getContext(), "未發現SD卡", Toast.LENGTH_SHORT).show();
- }
- }
- /**
- * 遍歷imageViewList中的每張圖片,對圖片的可見性進行檢查,如果圖片已經離開螢幕可見範圍,則將圖片替換成一張空圖。
- */
- public void checkVisibility() {
- for (int i = 0; i < imageViewList.size(); i++) {
- ImageView imageView = imageViewList.get(i);
- int borderTop = (Integer) imageView.getTag(R.string.border_top);
- int borderBottom = (Integer) imageView
- .getTag(R.string.border_bottom);
- if (borderBottom > getScrollY()
- && borderTop < getScrollY() + scrollViewHeight) {
- String imageUrl = (String) imageView.getTag(R.string.image_url);
- Bitmap bitmap = imageLoader.getBitmapFromMemoryCache(imageUrl);
- if (bitmap != null) {
- imageView.setImageBitmap(bitmap);
- } else {
- LoadImageTask task = new LoadImageTask(imageView);
- task.execute(imageUrl);
- }
- } else {
- imageView.setImageResource(R.drawable.empty_photo);
- }
- }
- }
- /**
- * 判斷手機是否有SD卡。
- *
- * @return 有SD卡返回true,沒有返回false。
- */
- private boolean hasSDCard() {
- return Environment.MEDIA_MOUNTED.equals(Environment
- .getExternalStorageState());
- }
- /**
- * 非同步下載圖片的任務。
- *
- * @author guolin
- */
- class LoadImageTask extends AsyncTask<String, Void, Bitmap> {
- /**
- * 圖片的URL地址
- */
- private String mImageUrl;
- /**
- * 可重複使用的ImageView
- */
- private ImageView mImageView;
- public LoadImageTask() {
- }
- /**
- * 將可重複使用的ImageView傳入
- *
- * @param imageView
- */
- public LoadImageTask(ImageView imageView) {
- mImageView = imageView;
- }
- @Override
- protected Bitmap doInBackground(String... params) {
- mImageUrl = params[0];
- Bitmap imageBitmap = imageLoader
- .getBitmapFromMemoryCache(mImageUrl);
- if (imageBitmap == null) {
- imageBitmap = loadImage(mImageUrl);
- }
- return imageBitmap;
- }
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap != null) {
- double ratio = bitmap.getWidth() / (columnWidth * 1.0);
- int scaledHeight = (int) (bitmap.getHeight() / ratio);
- addImage(bitmap, columnWidth, scaledHeight);
- }
- taskCollection.remove(this);
- }
- /**
- * 根據傳入的URL,對圖片進行載入。如果這張圖片已經存在於SD卡中,則直接從SD卡里讀取,否則就從網路上下載。
- *
- * @param imageUrl
- * 圖片的URL地址
- * @return 載入到記憶體的圖片。
- */
- private Bitmap loadImage(String imageUrl) {
- File imageFile = new File(getImagePath(imageUrl));
- if (!imageFile.exists()) {
- downloadImage(imageUrl);
- }
- if (imageUrl != null) {
- Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource(
- imageFile.getPath(), columnWidth);
- if (bitmap != null) {
- imageLoader.addBitmapToMemoryCache(imageUrl, bitmap);
- return bitmap;
- }
- }
- return null;
- }
- /**
- * 向ImageView中新增一張圖片
- *
- * @param bitmap
- * 待新增的圖片
- * @param imageWidth
- * 圖片的寬度
- * @param imageHeight
- * 圖片的高度
- */
- private void addImage(Bitmap bitmap, int imageWidth, int imageHeight) {
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
- imageWidth, imageHeight);
- if (mImageView != null) {
- mImageView.setImageBitmap(bitmap);
- } else {
- ImageView imageView = new ImageView(getContext());
- imageView.setLayoutParams(params);
- imageView.setImageBitmap(bitmap);
- imageView.setScaleType(ScaleType.FIT_XY);
- imageView.setPadding(5, 5, 5, 5);
- imageView.setTag(R.string.image_url, mImageUrl);
- findColumnToAdd(imageView, imageHeight).addView(imageView);
- imageViewList.add(imageView);
- }
- }
- /**
- * 找到此時應該新增圖片的一列。原則就是對三列的高度進行判斷,當前高度最小的一列就是應該新增的一列。
- *
- * @param imageView
- * @param imageHeight
- * @return 應該新增圖片的一列
- */
- private LinearLayout findColumnToAdd(ImageView imageView,
- int imageHeight) {
- if (firstColumnHeight <= secondColumnHeight) {
- if (firstColumnHeight <= thirdColumnHeight) {
- imageView.setTag(R.string.border_top, firstColumnHeight);
- firstColumnHeight += imageHeight;
- imageView.setTag(R.string.border_bottom, firstColumnHeight);
- return firstColumn;
- }
- imageView.setTag(R.string.border_top, thirdColumnHeight);
- thirdColumnHeight += imageHeight;
- imageView.setTag(R.string.border_bottom, thirdColumnHeight);
- return thirdColumn;
- } else {
- if (secondColumnHeight <= thirdColumnHeight) {
- imageView.setTag(R.string.border_top, secondColumnHeight);
- secondColumnHeight += imageHeight;
- imageView
- .setTag(R.string.border_bottom, secondColumnHeight);
- return secondColumn;
- }
- imageView.setTag(R.string.border_top, thirdColumnHeight);
- thirdColumnHeight += imageHeight;
- imageView.setTag(R.string.border_bottom, thirdColumnHeight);
- return thirdColumn;
- }
- }
- /**
- * 將圖片下載到SD卡快取起來。
- *
- * @param imageUrl
- * 圖片的URL地址。
- */
- private void downloadImage(String imageUrl) {
- HttpURLConnection con = null;
- FileOutputStream fos = null;
- BufferedOutputStream bos = null;
- BufferedInputStream bis = null;
- File imageFile = null;
- try {
- URL url = new URL(imageUrl);
- con = (HttpURLConnection) url.openConnection();
- con.setConnectTimeout(5 * 1000);
- con.setReadTimeout(15 * 1000);
- con.setDoInput(true);
- con.setDoOutput(true);
- bis = new BufferedInputStream(con.getInputStream());
- imageFile = new File(getImagePath(imageUrl));
- fos = new FileOutputStream(imageFile);
- bos = new BufferedOutputStream(fos);
- byte[] b = new byte[1024];
- int length;
- while ((length = bis.read(b)) != -1) {
- bos.write(b, 0, length);
- bos.flush();
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- if (bis != null) {
- bis.close();
- }
- if (bos != null) {
- bos.close();
- }
- if (con != null) {
- con.disconnect();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (imageFile != null) {
- Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource(
- imageFile.getPath(), columnWidth);
- if (bitmap != null) {
- imageLoader.addBitmapToMemoryCache(imageUrl, bitmap);
- }
- }
- }
- /**
- * 獲取圖片的本地儲存路徑。
- *
- * @param imageUrl
- * 圖片的URL地址。
- * @return 圖片的本地儲存路徑。
- */
- private String getImagePath(String imageUrl) {
- int lastSlashIndex = imageUrl.lastIndexOf("/");
- String imageName = imageUrl.substring(lastSlashIndex + 1);
- String imageDir = Environment.getExternalStorageDirectory()
- .getPath() + "/PhotoWallFalls/";
- File file = new File(imageDir);
- if (!file.exists()) {
- file.mkdirs();
- }
- String imagePath = imageDir + imageName;
- return imagePath;
- }
- }
- }
那我們就要來看一看loadMoreImages()方法的內部細節了。在這個方法中,使用了一個迴圈來載入這一頁中的每一張圖片,每次都會開啟一個LoadImageTask,用於對圖片進行非同步載入。然後在LoadImageTask中,首先會先檢查一下這張圖片是不是已經存在於SD卡中了,如果還沒存在,就從網路上下載,然後把這張圖片存放在LruCache中。接著將這張圖按照一定的比例進行壓縮,並找出當前高度最小的一列,把壓縮後的圖片新增進去就可以了。
另外,為了保證照片牆上的圖片都能夠合適地被回收,這裡還加入了一個可見性檢查的方法,即checkVisibility()方法。這個方法的核心思想就是檢查目前照片牆上的所有圖片,判斷出哪些是可見的,哪些是不可見。然後將那些不可見的圖片都替換成一張空圖,這樣就可以保證程式始終不會佔用過高的記憶體。當這些圖片又重新變為可見的時候,只需要再從LruCache中將這些圖片重新取出即可。如果某張圖片已經從LruCache中被移除了,就會開啟一個LoadImageTask,將這張圖片重新載入到記憶體中。
然後開啟或新建activity_main.xml,在裡面設定好瀑布流的佈局方式,如下所示:
- <com.example.photowallfallsdemo.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/my_scroll_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal" >
- <LinearLayout
- android:id="@+id/first_column"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="vertical" >
- </LinearLayout>
- <LinearLayout
- android:id="@+id/second_column"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="vertical" >
- </LinearLayout>
- <LinearLayout
- android:id="@+id/third_column"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="vertical" >
- </LinearLayout>
- </LinearLayout>
- </com.example.photowallfallsdemo.MyScrollView>
最後,由於我們使用到了網路和SD卡儲存的功能,因此還需要在AndroidManifest.xml中新增以下許可權:
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.INTERNET" />
瀑布流模式的照片牆果真非常美觀吧,而且由於我們有非常完善的資源釋放機制,不管你在照片牆上新增了多少圖片,程式佔用記憶體始終都會保持在一個合理的範圍內。在下一篇文章中,我會帶著大家對這個程式進行進一步的完善,加入點選檢視大圖,以及多點觸控縮放的功能,感覺興趣的朋友請繼續閱讀Android多點觸控技術實戰,自由地對圖片進行縮放和移動 。
好了,今天的講解到此結束,有疑問的朋友請在下面留言。
原始碼下載,請點選這裡相關文章
- 浮動流元素排列規則
- iOS瀑布流初體驗iOS
- vue實現瀑布流Vue
- 卡片瀑布流實現
- css實現瀑布流CSS
- 用C#實現不規則窗體C#
- 原生 js 實現瀑布流佈局、React 版本的瀑布流佈局元件JSReact元件
- jQuery實現瀑布流佈局jQuery
- 記錄:瀑布流最佳實現方案
- 用css實現不規則背景填充CSS
- 純CSS實現瀑布流,你會嗎?CSS
- 自定義collocationViewLayout實現多區瀑布流View
- Android照片牆加強版,使用ViewPager實現畫廊效果AndroidViewpager
- 在直播軟體搭建中有哪些可以實現瀑布流的的方法?
- UIColletionView瀑布流佈局實現思路以及封裝的實現UIView封裝
- Android使用LruCache、DiskLruCache實現圖片快取+圖片瀑布流Android快取
- 如何在直播軟體搭建中,實現圖片瀑布流效果?
- Android ListView功能擴充套件,實現高效能的瀑布流佈局AndroidView套件
- DDD中實現業務規則的驗證 - Marcin
- 也說說c++builder中的不規則窗體的實現 (轉)C++UI
- 在鴻蒙中實現類似瀑布流效果鴻蒙
- 原生 JS 實現一個瀑布流外掛JS
- 瀑布流佈局實現程式碼詳解
- 談談實現瀑布流佈局的幾種思路
- Android不規則圖形(1)Android
- iptables防火牆規則防火牆
- OpenStack 的防火牆規則流程防火牆
- 用VC++實現不規則視窗 (轉)C++
- 小米拼圖牆人工智慧系統評測:瀑布流讓內容源源不斷人工智慧
- ionic3實戰-隨機佈局瀑布流實現隨機
- iOS專案開發實戰——使用UICollectionView實現瀑布流iOSUIView
- Kali && Debain 防火牆規則AI防火牆
- Bootstrap實戰 - 瀑布流佈局boot
- sentinel流控規則校驗之原始碼分析原始碼
- 關於laravel使用自定義驗證規則後某些規則不生效Laravel
- CSS3實現3d效果照片牆CSSS33D
- Java程式碼實現七夕魔方照片牆Java
- 用html5實現圖片的旋轉--照片牆HTML