- 浏览: 448807 次
文章分类
最新评论
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类,将所有相册中图片的网址都配置进去,代码如下所示:
- publicclassImages{
- publicfinalstaticString[]imageUrls=newString[]{
- "http://img.my.csdn.net/uploads/201309/01/1378037235_3453.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037235_9280.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037234_3539.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037234_6318.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037194_2965.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037193_1687.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037193_1286.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037192_8379.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037178_9374.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037177_1254.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037177_6203.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037152_6352.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037151_9565.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037151_7904.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037148_7104.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037129_8825.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037128_5291.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037128_3531.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037127_1085.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037095_7515.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037094_8001.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037093_7168.jpg",
- "http://img.my.csdn.net/uploads/201309/01/1378037091_4950.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949643_6410.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949642_6939.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949630_4505.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949630_4593.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949629_7309.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949629_8247.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949615_1986.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949614_8482.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949614_3743.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949614_4199.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949599_3416.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949599_5269.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949598_7858.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949598_9982.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949578_2770.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949578_8744.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949577_5210.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949577_1998.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949482_8813.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949481_6577.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949480_4490.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949455_6792.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949455_6345.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949442_4553.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949441_8987.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949441_5454.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949454_6367.jpg",
- "http://img.my.csdn.net/uploads/201308/31/1377949442_4562.jpg"};
- }
- publicclassImageLoader{
- /**
- *图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
- */
- privatestaticLruCache<String,Bitmap>mMemoryCache;
- /**
- *ImageLoader的实例。
- */
- privatestaticImageLoadermImageLoader;
- privateImageLoader(){
- //获取应用程序最大可用内存
- intmaxMemory=(int)Runtime.getRuntime().maxMemory();
- intcacheSize=maxMemory/8;
- //设置图片缓存大小为程序最大可用内存的1/8
- mMemoryCache=newLruCache<String,Bitmap>(cacheSize){
- @Override
- protectedintsizeOf(Stringkey,Bitmapbitmap){
- returnbitmap.getByteCount();
- }
- };
- }
- /**
- *获取ImageLoader的实例。
- *
- *@returnImageLoader的实例。
- */
- publicstaticImageLoadergetInstance(){
- if(mImageLoader==null){
- mImageLoader=newImageLoader();
- }
- returnmImageLoader;
- }
- /**
- *将一张图片存储到LruCache中。
- *
- *@paramkey
- *LruCache的键,这里传入图片的URL地址。
- *@parambitmap
- *LruCache的键,这里传入从网络上下载的Bitmap对象。
- */
- publicvoidaddBitmapToMemoryCache(Stringkey,Bitmapbitmap){
- if(getBitmapFromMemoryCache(key)==null){
- mMemoryCache.put(key,bitmap);
- }
- }
- /**
- *从LruCache中获取一张图片,如果不存在就返回null。
- *
- *@paramkey
- *LruCache的键,这里传入图片的URL地址。
- *@return对应传入键的Bitmap对象,或者null。
- */
- publicBitmapgetBitmapFromMemoryCache(Stringkey){
- returnmMemoryCache.get(key);
- }
- publicstaticintcalculateInSampleSize(BitmapFactory.Optionsoptions,
- intreqWidth){
- //源图片的宽度
- finalintwidth=options.outWidth;
- intinSampleSize=1;
- if(width>reqWidth){
- //计算出实际宽度和目标宽度的比率
- finalintwidthRatio=Math.round((float)width/(float)reqWidth);
- inSampleSize=widthRatio;
- }
- returninSampleSize;
- }
- publicstaticBitmapdecodeSampledBitmapFromResource(StringpathName,
- intreqWidth){
- //第一次解析将inJustDecodeBounds设置为true,来获取图片大小
- finalBitmapFactory.Optionsoptions=newBitmapFactory.Options();
- options.inJustDecodeBounds=true;
- BitmapFactory.decodeFile(pathName,options);
- //调用上面定义的方法计算inSampleSize值
- options.inSampleSize=calculateInSampleSize(options,reqWidth);
- //使用获取到的inSampleSize值再次解析图片
- options.inJustDecodeBounds=false;
- returnBitmapFactory.decodeFile(pathName,options);
- }
- }
接下来新建MyScrollView继承自ScrollView,代码如下所示:
- publicclassMyScrollViewextendsScrollViewimplementsOnTouchListener{
- /**
- *每页要加载的图片数量
- */
- publicstaticfinalintPAGE_SIZE=15;
- /**
- *记录当前已加载到第几页
- */
- privateintpage;
- /**
- *每一列的宽度
- */
- privateintcolumnWidth;
- /**
- *当前第一列的高度
- */
- privateintfirstColumnHeight;
- /**
- *当前第二列的高度
- */
- privateintsecondColumnHeight;
- /**
- *当前第三列的高度
- */
- privateintthirdColumnHeight;
- /**
- *是否已加载过一次layout,这里onLayout中的初始化只需加载一次
- */
- privatebooleanloadOnce;
- /**
- *对图片进行管理的工具类
- */
- privateImageLoaderimageLoader;
- /**
- *第一列的布局
- */
- privateLinearLayoutfirstColumn;
- /**
- *第二列的布局
- */
- privateLinearLayoutsecondColumn;
- /**
- *第三列的布局
- */
- privateLinearLayoutthirdColumn;
- /**
- *记录所有正在下载或等待下载的任务。
- */
- privatestaticSet<LoadImageTask>taskCollection;
- /**
- *MyScrollView下的直接子布局。
- */
- privatestaticViewscrollLayout;
- /**
- *MyScrollView布局的高度。
- */
- privatestaticintscrollViewHeight;
- /**
- *记录上垂直方向的滚动距离。
- */
- privatestaticintlastScrollY=-1;
- /**
- *记录所有界面上的图片,用以可以随时控制对图片的释放。
- */
- privateList<ImageView>imageViewList=newArrayList<ImageView>();
- /**
- *在Handler中进行图片可见性检查的判断,以及加载更多图片的操作。
- */
- privatestaticHandlerhandler=newHandler(){
- publicvoidhandleMessage(android.os.Messagemsg){
- MyScrollViewmyScrollView=(MyScrollView)msg.obj;
- intscrollY=myScrollView.getScrollY();
- //如果当前的滚动位置和上次相同,表示已停止滚动
- if(scrollY==lastScrollY){
- //当滚动的最底部,并且当前没有正在下载的任务时,开始加载下一页的图片
- if(scrollViewHeight+scrollY>=scrollLayout.getHeight()
- &&taskCollection.isEmpty()){
- myScrollView.loadMoreImages();
- }
- myScrollView.checkVisibility();
- }else{
- lastScrollY=scrollY;
- Messagemessage=newMessage();
- message.obj=myScrollView;
- //5毫秒后再次对滚动位置进行判断
- handler.sendMessageDelayed(message,5);
- }
- };
- };
- /**
- *MyScrollView的构造函数。
- *
- *@paramcontext
- *@paramattrs
- */
- publicMyScrollView(Contextcontext,AttributeSetattrs){
- super(context,attrs);
- imageLoader=ImageLoader.getInstance();
- taskCollection=newHashSet<LoadImageTask>();
- setOnTouchListener(this);
- }
- /**
- *进行一些关键性的初始化操作,获取MyScrollView的高度,以及得到第一列的宽度值。并在这里开始加载第一页的图片。
- */
- @Override
- protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
- 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
- publicbooleanonTouch(Viewv,MotionEventevent){
- if(event.getAction()==MotionEvent.ACTION_UP){
- Messagemessage=newMessage();
- message.obj=this;
- handler.sendMessageDelayed(message,5);
- }
- returnfalse;
- }
- /**
- *开始加载下一页的图片,每张图片都会开启一个异步线程去下载。
- */
- publicvoidloadMoreImages(){
- if(hasSDCard()){
- intstartIndex=page*PAGE_SIZE;
- intendIndex=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(inti=startIndex;i<endIndex;i++){
- LoadImageTasktask=newLoadImageTask();
- 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中的每张图片,对图片的可见性进行检查,如果图片已经离开屏幕可见范围,则将图片替换成一张空图。
- */
- publicvoidcheckVisibility(){
- for(inti=0;i<imageViewList.size();i++){
- ImageViewimageView=imageViewList.get(i);
- intborderTop=(Integer)imageView.getTag(R.string.border_top);
- intborderBottom=(Integer)imageView
- .getTag(R.string.border_bottom);
- if(borderBottom>getScrollY()
- &&borderTop<getScrollY()+scrollViewHeight){
- StringimageUrl=(String)imageView.getTag(R.string.image_url);
- Bitmapbitmap=imageLoader.getBitmapFromMemoryCache(imageUrl);
- if(bitmap!=null){
- imageView.setImageBitmap(bitmap);
- }else{
- LoadImageTasktask=newLoadImageTask(imageView);
- task.execute(imageUrl);
- }
- }else{
- imageView.setImageResource(R.drawable.empty_photo);
- }
- }
- }
- /**
- *判断手机是否有SD卡。
- *
- *@return有SD卡返回true,没有返回false。
- */
- privatebooleanhasSDCard(){
- returnEnvironment.MEDIA_MOUNTED.equals(Environment
- .getExternalStorageState());
- }
- /**
- *异步下载图片的任务。
- *
- *@authorguolin
- */
- classLoadImageTaskextendsAsyncTask<String,Void,Bitmap>{
- /**
- *图片的URL地址
- */
- privateStringmImageUrl;
- /**
- *可重复使用的ImageView
- */
- privateImageViewmImageView;
- publicLoadImageTask(){
- }
- /**
- *将可重复使用的ImageView传入
- *
- *@paramimageView
- */
- publicLoadImageTask(ImageViewimageView){
- mImageView=imageView;
- }
- @Override
- protectedBitmapdoInBackground(String...params){
- mImageUrl=params[0];
- BitmapimageBitmap=imageLoader
- .getBitmapFromMemoryCache(mImageUrl);
- if(imageBitmap==null){
- imageBitmap=loadImage(mImageUrl);
- }
- returnimageBitmap;
- }
- @Override
- protectedvoidonPostExecute(Bitmapbitmap){
- if(bitmap!=null){
- doubleratio=bitmap.getWidth()/(columnWidth*1.0);
- intscaledHeight=(int)(bitmap.getHeight()/ratio);
- addImage(bitmap,columnWidth,scaledHeight);
- }
- taskCollection.remove(this);
- }
- /**
- *根据传入的URL,对图片进行加载。如果这张图片已经存在于SD卡中,则直接从SD卡里读取,否则就从网络上下载。
- *
- *@paramimageUrl
- *图片的URL地址
- *@return加载到内存的图片。
- */
- privateBitmaploadImage(StringimageUrl){
- FileimageFile=newFile(getImagePath(imageUrl));
- if(!imageFile.exists()){
- downloadImage(imageUrl);
- }
- if(imageUrl!=null){
- Bitmapbitmap=ImageLoader.decodeSampledBitmapFromResource(
- imageFile.getPath(),columnWidth);
- if(bitmap!=null){
- imageLoader.addBitmapToMemoryCache(imageUrl,bitmap);
- returnbitmap;
- }
- }
- returnnull;
- }
- /**
- *向ImageView中添加一张图片
- *
- *@parambitmap
- *待添加的图片
- *@paramimageWidth
- *图片的宽度
- *@paramimageHeight
- *图片的高度
- */
- privatevoidaddImage(Bitmapbitmap,intimageWidth,intimageHeight){
- LinearLayout.LayoutParamsparams=newLinearLayout.LayoutParams(
- imageWidth,imageHeight);
- if(mImageView!=null){
- mImageView.setImageBitmap(bitmap);
- }else{
- ImageViewimageView=newImageView(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);
- }
- }
- /**
- *找到此时应该添加图片的一列。原则就是对三列的高度进行判断,当前高度最小的一列就是应该添加的一列。
- *
- *@paramimageView
- *@paramimageHeight
- *@return应该添加图片的一列
- */
- privateLinearLayoutfindColumnToAdd(ImageViewimageView,
- intimageHeight){
- if(firstColumnHeight<=secondColumnHeight){
- if(firstColumnHeight<=thirdColumnHeight){
- imageView.setTag(R.string.border_top,firstColumnHeight);
- firstColumnHeight+=imageHeight;
- imageView.setTag(R.string.border_bottom,firstColumnHeight);
- returnfirstColumn;
- }
- imageView.setTag(R.string.border_top,thirdColumnHeight);
- thirdColumnHeight+=imageHeight;
- imageView.setTag(R.string.border_bottom,thirdColumnHeight);
- returnthirdColumn;
- }else{
- if(secondColumnHeight<=thirdColumnHeight){
- imageView.setTag(R.string.border_top,secondColumnHeight);
- secondColumnHeight+=imageHeight;
- imageView
- .setTag(R.string.border_bottom,secondColumnHeight);
- returnsecondColumn;
- }
- imageView.setTag(R.string.border_top,thirdColumnHeight);
- thirdColumnHeight+=imageHeight;
- imageView.setTag(R.string.border_bottom,thirdColumnHeight);
- returnthirdColumn;
- }
- }
- /**
- *将图片下载到SD卡缓存起来。
- *
- *@paramimageUrl
- *图片的URL地址。
- */
- privatevoiddownloadImage(StringimageUrl){
- HttpURLConnectioncon=null;
- FileOutputStreamfos=null;
- BufferedOutputStreambos=null;
- BufferedInputStreambis=null;
- FileimageFile=null;
- try{
- URLurl=newURL(imageUrl);
- con=(HttpURLConnection)url.openConnection();
- con.setConnectTimeout(5*1000);
- con.setReadTimeout(15*1000);
- con.setDoInput(true);
- con.setDoOutput(true);
- bis=newBufferedInputStream(con.getInputStream());
- imageFile=newFile(getImagePath(imageUrl));
- fos=newFileOutputStream(imageFile);
- bos=newBufferedOutputStream(fos);
- byte[]b=newbyte[1024];
- intlength;
- while((length=bis.read(b))!=-1){
- bos.write(b,0,length);
- bos.flush();
- }
- }catch(Exceptione){
- e.printStackTrace();
- }finally{
- try{
- if(bis!=null){
- bis.close();
- }
- if(bos!=null){
- bos.close();
- }
- if(con!=null){
- con.disconnect();
- }
- }catch(IOExceptione){
- e.printStackTrace();
- }
- }
- if(imageFile!=null){
- Bitmapbitmap=ImageLoader.decodeSampledBitmapFromResource(
- imageFile.getPath(),columnWidth);
- if(bitmap!=null){
- imageLoader.addBitmapToMemoryCache(imageUrl,bitmap);
- }
- }
- }
- /**
- *获取图片的本地存储路径。
- *
- *@paramimageUrl
- *图片的URL地址。
- *@return图片的本地存储路径。
- */
- privateStringgetImagePath(StringimageUrl){
- intlastSlashIndex=imageUrl.lastIndexOf("/");
- StringimageName=imageUrl.substring(lastSlashIndex+1);
- StringimageDir=Environment.getExternalStorageDirectory()
- .getPath()+"/PhotoWallFalls/";
- Filefile=newFile(imageDir);
- if(!file.exists()){
- file.mkdirs();
- }
- StringimagePath=imageDir+imageName;
- returnimagePath;
- }
- }
- }
MyScrollView是实现瀑布流照片墙的核心类,这里我来重点给大家介绍一下。首先它是继承自ScrollView的,这样就允许用户可以通过滚动的方式来浏览更多的图片。这里提供了一个loadMoreImages()方法,是专门用于加载下一页的图片的,因此在onLayout()方法中我们要先调用一次这个方法,以初始化第一页的图片。然后在onTouch方法中每当监听到手指离开屏幕的事件,就会通过一个handler来对当前ScrollView的滚动状态进行判断,如果发现已经滚动到了最底部,就会再次调用loadMoreImages()方法去加载下一页的图片。
那我们就要来看一看loadMoreImages()方法的内部细节了。在这个方法中,使用了一个循环来加载这一页中的每一张图片,每次都会开启一个LoadImageTask,用于对图片进行异步加载。然后在LoadImageTask中,首先会先检查一下这张图片是不是已经存在于SD卡中了,如果还没存在,就从网络上下载,然后把这张图片存放在LruCache中。接着将这张图按照一定的比例进行压缩,并找出当前高度最小的一列,把压缩后的图片添加进去就可以了。
另外,为了保证照片墙上的图片都能够合适地被回收,这里还加入了一个可见性检查的方法,即checkVisibility()方法。这个方法的核心思想就是检查目前照片墙上的所有图片,判断出哪些是可见的,哪些是不可见。然后将那些不可见的图片都替换成一张空图,这样就可以保证程序始终不会占用过高的内存。当这些图片又重新变为可见的时候,只需要再从LruCache中将这些图片重新取出即可。如果某张图片已经从LruCache中被移除了,就会开启一个LoadImageTask,将这张图片重新加载到内存中。
然后打开或新建activity_main.xml,在里面设置好瀑布流的布局方式,如下所示:
- <com.example.photowallfallsdemo.MyScrollViewxmlns: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-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <uses-permissionandroid:name="android.permission.INTERNET"/>
瀑布流模式的照片墙果真非常美观吧,而且由于我们有非常完善的资源释放机制,不管你在照片墙上添加了多少图片,程序占用内存始终都会保持在一个合理的范围内。在下一篇文章中,我会带着大家对这个程序进行进一步的完善,加入点击查看大图,以及多点触控缩放的功能,感觉兴趣的朋友请继续阅读Android多点触控技术实战,自由地对图片进行缩放和移动。
好了,今天的讲解到此结束,有疑问的朋友请在下面留言。
相关推荐
- PhotoWallFallsDemo:这是一个Android瀑布流照片墙的实现示例,包含了瀑布流布局的基本功能,如图片加载、滚动监听和动态添加数据等。通过阅读和学习此示例代码,可以深入理解瀑布流布局的实现过程。 综上所述,...
本教程将围绕“Android瀑布流照片墙实现”这一主题展开,介绍如何构建一个具有类似功能的Demo。 首先,我们要理解瀑布流的核心概念。瀑布流布局的特点是每个单元格的高度不固定,根据内容自适应,通常采用递归分组...
Android瀑布流照片墙实现,体验不规则排列的美感.zip项目安卓应用源码下载Android瀑布流照片墙实现,体验不规则排列的美感.zip项目安卓应用源码下载 1.适合学生毕业设计研究参考 2.适合个人学习研究参考 3.适合公司...
在Android开发中,创建一个不规则瀑布流照片墙是一项挑战性的任务,但其结果能带给用户独特的视觉体验。本文将深入探讨如何实现这样一个功能,主要关注以下知识点: 1. **布局设计**:不规则瀑布流的核心在于每个...
这个压缩包“Android瀑布流照片墙实现,体验不规则排列的美感”提供了一个具体的实现示例,对于学习Android应用源码和论文撰写具有很高的参考价值。 首先,瀑布流布局的核心在于自适应和滚动的流畅性。在Android中...
本项目“Android瀑布流照片墙实现,体验不规则排列的美感”旨在帮助开发者理解并掌握如何在Android平台上构建这样的功能。 首先,我们需要了解瀑布流布局的核心思想。瀑布流布局通常由两个主要部分组成:一个负责...
总的来说,实现Android不规则瀑布流照片墙需要对Android的UI组件有深入理解,特别是Recycler View和自定义LayoutManager。同时,还需要掌握图片加载库的使用以及性能优化技巧。通过以上步骤,你可以为用户打造一个既...
在这个"Android应用源码之Android瀑布流照片墙实现,体验不规则排列的美感"的DEMO中,我们可以深入学习如何在Android平台上构建一个类似的图片浏览应用。 首先,瀑布流的核心在于布局管理器。在Android中,我们通常...
这个名为“Android瀑布流照片墙实现,体验不规则排列的美感”的压缩包中包含了一个实现这一功能的源代码项目。 瀑布流布局的核心思想是利用滚动事件,动态计算每个单元格的高度,并根据屏幕宽度和当前元素的宽度...
本资源提供的“Android瀑布流照片墙实现,体验不规则排列的美感.zip”是一个实现了瀑布流布局的源码项目,主要涉及的技术点包括: 1. **RecyclerView**:在Android中,RecyclerView是用于显示可滚动列表的视图容器...
这个“Android高级应用源码-Android瀑布流照片墙实现”项目,旨在提供一种实现瀑布流布局的实例,帮助开发者理解和实践此类设计。 瀑布流布局的核心在于自适应屏幕大小和图片尺寸,以及动态加载更多的数据。以下是...
总结,实现Android瀑布流照片墙需要理解RecyclerView的工作原理,自定义LayoutManager以实现不规则排列,选择合适的图片加载库,以及处理滚动事件和数据加载。通过这些技术手段,我们可以为用户提供一个流畅且美观的...
本项目“安卓Android源码——安卓Android瀑布流照片墙实现”提供了这样一个实现示例,让我们深入探讨一下其背后的实现原理和技术要点。 首先,瀑布流布局的核心在于自适应屏幕和动态调整列数。在Android中,我们...
本项目“Android应用源码之Android瀑布流照片墙实现”提供了完整的源码,非常适合Android开发初学者进行毕业设计学习。下面将详细介绍瀑布流布局的核心概念、实现原理以及在Android中的应用。 1. **瀑布流布局介绍*...