位置: 编程技术 - 正文

Android瀑布流照片墙实现,体验不规则排列的美感(安卓瀑布流)

编辑:rootadmin

推荐整理分享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中。接着将这张图按照一定的比例进行压缩,并找出当前高度最小的一列,把压缩后的图片添加进去就可以了。

Android瀑布流照片墙实现,体验不规则排列的美感(安卓瀑布流)

另外,为了保证照片墙上的图片都能够合适地被回收,这里还加入了一个可见性检查的方法,即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 转载请保留说明!

上一篇:Android高效加载大图、多图解决方案,有效避免程序OOM(android 加载更多)

下一篇:Android多点触控技术实战,自由地对图片进行缩放和移动(android多指触控)

  • 法定免税项目包括
  • 等线补交以前年度附加税会计分录
  • 房屋维修基金帐户怎么查
  • 基本存款账户可以换银行吗?
  • 社保漏缴1个月怎么补缴
  • 小规模纳税人是个体户吗
  • 如何开商业承兑汇票业务
  • 税务管理相关知识
  • 继承的房产出售要交20%是全额还是差额
  • 增值税发票的开具问题
  • 个贷系统平账专户利息分录
  • 案例分析个人心得体会
  • 会计案例分析题万能模板
  • 纳税人跨县(市、区)提供建筑服务
  • 财务报表依据的会计记录
  • 个人股权转让个税计算
  • 固定资产入账包括税额吗
  • 长期债权投资业务处理
  • 工厂的劳务加工有哪些
  • 如何看懂财务报表的书
  • 计提的工资大于实际发放的
  • 如何向企业支付宝收款
  • 失控发票是什么
  • 猜单词游戏代码
  • qqexternal.exe是什么进程如何删除(CPU的使用率在90%)
  • element ui datepicker 源码
  • 购进油漆,用于装饰本企业办公楼
  • 小狐狸803050
  • 真正有效解决近视的方法
  • 经济补偿金个人所得税计算公式
  • 所有非批扣和所有批扣是什么意思
  • php判断是手机端还是端口
  • 职工教育经费能结转几年
  • 怎样利用 getnext 命令检索未知对象?
  • 应付账款盘点表
  • golang 调用动态库
  • 单位收的房租可以发工资吗
  • python如何对齐输出
  • syslog ng
  • 铝合金门窗行业利润率
  • 分公司是独立的诉讼主体吗
  • 购入交易性金融资产的相关税费计入
  • 仓库物资盘盈盘亏怎么办
  • 福利费的进项税怎么做账
  • 运输费属于生产成本还是制造费用
  • 发货环节产生的影响
  • 租金收入什么时候交房产税
  • 计提合同印花税怎么做账
  • 多计提的个人所得税退给员工怎么处理
  • 固定资产一次性扣除后第二年账务处理
  • 财务费用怎么用
  • 收回应收账款账面余额
  • 购买材料计入
  • 会计软件入什么科目
  • 其他收入属于什么收入
  • win7 mysql5.7.21安装
  • mysql如何导出数据
  • win8系统升级
  • 电脑久了就会高温
  • win10如何添加蓝牙驱动
  • spyware.exe - spyware是什么进程
  • win8怎样恢复系统
  • win10资源管理器快捷键
  • thinkpade431升级内存多大好
  • unity项目资源
  • jQuery扩展+xml实现表单验证功能的方法
  • 读取fasta文件
  • 基于stm32的100个毕业设计
  • xp启动项 命令
  • jquery操作html代码
  • android 加载大图
  • js如何实现重置功能
  • python中的堆和栈
  • flask框架下使用scrapy框架
  • 龙华区有几个地方
  • 报税时一直显示申报中
  • 陕西国家税务
  • 北京市国家税务局网站官网
  • 分支机构如何变更为独立纳税人?
  • 常设机构怎么缴纳所得税
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设