位置: 编程技术 - 正文
推荐整理分享Android瀑布流照片墙实现,体验不规则排列的美感(安卓瀑布流),希望有所帮助,仅作参考,欢迎阅读内容。
文章相关热门搜索词:安卓瀑布流,android瀑布流布局 滑动错位,瀑布流相册,android瀑布流布局 滑动错位,android recyclerview 瀑布流,android瀑布流布局 滑动错位,安卓开发瀑布流ui,瀑布流相册,内容如对您有帮助,希望把文章链接给更多的朋友!
转载请注明出处:
听我这么说完后,你可能会觉得瀑布流的布局非常简单嘛,只需要使用三个LinearLayout平分整个屏幕宽度,然后动态地addView()进去就好了。确实如此,如果只是为了实现功能的话,就是这么简单。可是别忘了,我们是在手机上进行开发,如果不停地往LinearLayout里添加图片,程序很快就会OOM。因此我们还需要一个合理的方案来对图片资源进行释放,这里仍然是准备使用LruCache算法,对这个算法不熟悉的朋友可以先参考Android高效加载大图、多图方案,有效避免程序OOM 。
下面我们就来开始实现吧,新建一个Android项目,起名叫PhotoWallFallsDemo,并选择4.0的API。
第一个要考虑的问题是,我们到哪儿去收集这些大小参差不齐的图片呢?这里我事先在百度上搜索了很多张风景图片,并且为了保证它们访问的稳定性,我将这些图片都上传到了我的CSDN相册里,因此只要从这里下载图片就可以了。新建一个Images类,将所有相册中图片的网址都配置进去,代码如下所示:
[java] view plaincopypublic class Images { public final static String[] imageUrls = new String[] { " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " }; } 然后新建一个ImageLoader类,用于方便对图片进行管理,代码如下所示:[java] view plaincopypublic 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); } } 这里我们将ImageLoader类设成单例,并在构造函数中初始化了LruCache类,把它的最大缓存容量设为最大可用内存的1/8。然后又提供了其它几个方法可以操作LruCache,以及对图片进行压缩和读取。接下来新建MyScrollView继承自ScrollView,代码如下所示:
[java] view plaincopypublic class MyScrollView extends ScrollView implements OnTouchListener { /** * 每页要加载的图片数量 */ public static final int PAGE_SIZE = ; /** * 记录当前已加载到第几页 */ 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 * ); con.setReadTimeout( * ); 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[]; 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; } } } MyScrollView是实现瀑布流照片墙的核心类,这里我来重点给大家介绍一下。首先它是继承自ScrollView的,这样就允许用户可以通过滚动的方式来浏览更多的图片。这里提供了一个loadMoreImages()方法,是专门用于加载下一页的图片的,因此在onLayout()方法中我们要先调用一次这个方法,以初始化第一页的图片。然后在onTouch方法中每当监听到手指离开屏幕的事件,就会通过一个handler来对当前ScrollView的滚动状态进行判断,如果发现已经滚动到了最底部,就会再次调用loadMoreImages()方法去加载下一页的图片。那我们就要来看一看loadMoreImages()方法的内部细节了。在这个方法中,使用了一个循环来加载这一页中的每一张图片,每次都会开启一个LoadImageTask,用于对图片进行异步加载。然后在LoadImageTask中,首先会先检查一下这张图片是不是已经存在于SD卡中了,如果还没存在,就从网络上下载,然后把这张图片存放在LruCache中。接着将这张图按照一定的比例进行压缩,并找出当前高度最小的一列,把压缩后的图片添加进去就可以了。
另外,为了保证照片墙上的图片都能够合适地被回收,这里还加入了一个可见性检查的方法,即checkVisibility()方法。这个方法的核心思想就是检查目前照片墙上的所有图片,判断出哪些是可见的,哪些是不可见。然后将那些不可见的图片都替换成一张空图,这样就可以保证程序始终不会占用过高的内存。当这些图片又重新变为可见的时候,只需要再从LruCache中将这些图片重新取出即可。如果某张图片已经从LruCache中被移除了,就会开启一个LoadImageTask,将这张图片重新加载到内存中。
然后打开或新建activity_main.xml,在里面设置好瀑布流的布局方式,如下所示:
[html] view plaincopy<com.example.photowallfallsdemo.MyScrollView xmlns: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> 可以看到,这里我们使用了刚才编写好的MyScrollView作为根布局,然后在里面放入了一个直接子布局LinearLayout用于统计当前滑动布局的高度,然后在这个布局下又添加了三个等宽的LinearLayout分别作为第一列、第二列和第三列的布局,这样在MyScrollView中就可以动态地向这三个LinearLayout里添加图片了。最后,由于我们使用到了网络和SD卡存储的功能,因此还需要在AndroidManifest.xml中添加以下权限:
[html] view plaincopy<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERNET" /> 这样我们所有的编码工作就已经完成了,现在可以尝试运行一下,效果如下图所示:
瀑布流模式的照片墙果真非常美观吧,而且由于我们有非常完善的资源释放机制,不管你在照片墙上添加了多少图片,程序占用内存始终都会保持在一个合理的范围内。在下一篇文章中,我会带着大家对这个程序进行进一步的完善,加入点击查看大图,以及多点触控缩放的功能,感觉兴趣的朋友请继续阅读Android多点触控技术实战,自由地对图片进行缩放和移动 。
好了,今天的讲解到此结束,有疑问的朋友请在下面留言。
源码下载,请点击这里
Android多点触控技术实战,自由地对图片进行缩放和移动 转载请注明出处:
关于安卓截取缩略图的方法和遇到的问题 最近的一个项目,需要写到录制视频,然后截取缩略图显示的功能。在以前的写法是调用系统自带的摄像的功能,但是效果不佳。后来打算自己写一个
浏览在线图片 MainActivity.javapackagecom.example.internetimageview;importjava.io.IOException;importjava.io.InputStream;importjava.net.HttpURLConnection;importjava.net.MalformedURLException;importjava.net.URL;impor
标签: 安卓瀑布流
本文链接地址:https://www.jiuchutong.com/biancheng/382170.html 转载请保留说明!友情链接: 武汉网站建设