The traditional layout of the interface is always a clear line, located in an orderly manner, this layout is commonplace, in imperceptible everyone has been to it has produced aesthetic fatigue. This time the waterfall flow layout of the emergence of the people brought a refreshing feeling, this layout although seemingly irregular, but there is a sense of beauty, so that the emergence of a large number of Web sites and applications have to use this novel layout to design the interface.
I remember writing an article about how to implement a photo wall on Android, but at that time the GridView was used to lay out the layout that only works for each picture on the wall, if the size of the picture is uneven, It would be very ugly to show in the GridView. The waterfall flow layout is a good way to solve this problem, so today we're going to catch up on the trend and see how we can achieve the function of the waterfall streaming photo wall on Android.
First or talk about the implementation of the principle, waterfall flow layout Although it seems to arrange a very casual, in fact it is a very scientific arrangement of rules. The entire interface will be divided into several columns of equal width according to the width of the screen, because the cell phone's screen is not very large, here we are divided into three columns. Whenever you need to add a picture, compress the width of the picture as wide as the column, compress the height of the picture by the same compression ratio, and then find the smallest column in the three columns and add the picture to the column. Then whenever you need to add a new picture, to repeat the above operation, it will form a waterfall pattern of the photo wall, the schematic below is shown below.
After listening to this, you may find that the layout of the waterfall flow is very simple, only need to use three linearlayout to split the width of the entire screen, and then dynamically AddView (). Indeed, if only to achieve the function, it is so simple. But don't forget, we are on the mobile phone development, if keep to linearlayout add pictures, the program will soon oom. So we also need a reasonable solution to the image resource release, here is still ready to use the LRUCache algorithm, for this algorithm is unfamiliar to friends can first refer to the Android high-performance loading large graphics, multiple graphics scheme, effectively avoid the program oom.
Let's start with a new Android project, called Photowallfallsdemo, and choose the 4.0 API.
The first question to consider is where are we going to collect pictures of these uneven sizes? Here I have in advance in Baidu search for a lot of scenery pictures, and in order to ensure the stability of their access, I have these pictures uploaded to my csdn album, so as long as the download from here to the picture. Create a new images class that configures the URLs of all the pictures in the album, as shown in the following code:
public class Images {public final static string[] Imageurls = new string[] {"http://img.my.csdn.net/uploads/20130 9/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 "};
}
Then create a new Imageloader class for easy administration of the picture, as shown in the following code:
The core class of the public class Imageloader {/** * Image caching technology is used to cache all downloaded pictures and remove the least recently used picture when the program memory reaches its set value.
* private static lrucache<string, bitmap> Mmemorycache;
Examples of/** * imageloader.
* * private static Imageloader Mimageloader;
Private Imageloader () {//Get application maximum available memory int maxmemory = (int) runtime.getruntime (). MaxMemory ();
int cacheSize = MAXMEMORY/8; Set the picture cache size to the program's maximum available memory 1/8 Mmemorycache = new lrucache<string, bitmap> (cacheSize) {@Override protected int size
Of (String key, Bitmap Bitmap) {return bitmap.getbytecount ();
}
};
/** * Gets an instance of the Imageloader.
* * @return Examples of imageloader.
*/public static Imageloader getinstance () {if (Mimageloader = null) {Mimageloader = new Imageloader ();
return mimageloader;
/** * Stores a picture in the LRUCache.
* @param key * LRUCache, where the URL address of the image is passed in.
* @param bitmap * LRUCache key, where bitmap objects downloaded from the network are passed in. */public void Addbitmaptomemorycache (String key, Bitmap Bitmap) {if (GETBITMAPFRommemorycache (key) = = null) {Mmemorycache.put (key, bitmap);
}/** * Gets a picture from the LRUCache and returns null if it does not exist.
* @param key * LRUCache, where the URL address of the image is passed in.
* @return The Bitmap object that corresponds to the incoming key, or null.
* * Public Bitmap Getbitmapfrommemorycache (String key) {return mmemorycache.get (key); The width of the public static int calculateinsamplesize (bitmapfactory.options Options, int reqwidth) {//source picture is final int wid
th = options.outwidth;
int insamplesize = 1;
if (Width > Reqwidth) {//The ratio of the actual width and the target width is final int widthRatio = Math.Round ((float) width/(float) reqwidth);
Insamplesize = WidthRatio;
return insamplesize; public static Bitmap Decodesampledbitmapfromresource (String pathName, int reqwidth) {//First parse will Injustdecodebound
s set to True to get picture size final bitmapfactory.options Options = new Bitmapfactory.options ();
Options.injustdecodebounds = true;
Bitmapfactory.decodefile (pathName, Options); Call the method defined above to compute the insamplesize value options.insamplesize = CalculateinsaMplesize (options, reqwidth);
Resolves the picture Options.injustdecodebounds = False again using the obtained insamplesize value;
Return Bitmapfactory.decodefile (PathName, Options);
}
}
Here we set the Imageloader class to a single example and initialize the LRUCache class in the constructor to set its maximum cache size to 1/8 of the maximum available memory. It then provides several other ways to manipulate LRUCache and to compress and read pictures.
Next, the new Myscrollview inherits from ScrollView, and the code looks like this:
public class Myscrollview extends ScrollView implements Ontouchlistener {/** * number of pictures to load per page/public static fin
Al int page_size = 15;
/** * record is currently loaded onto page/private int page;
/** * The width of each column * * private int columnWidth;
/** * The height of the current first column * * private int firstcolumnheight;
/** * Current second column height * * private int secondcolumnheight;
/** * The height of the current third column * * private int thirdcolumnheight;
/** * Whether a layout has been loaded once, where initialization in onlayout can only be loaded once/private Boolean loadonce;
/** * To manage the image of the tool class * * Private Imageloader Imageloader;
/** * The layout of the first column * * Private linearlayout firstcolumn;
/** * The second column of the layout * * Private linearlayout secondcolumn;
/** * The layout of the third column * * Private linearlayout thirdcolumn;
/** * Record All tasks that are downloading or waiting to be downloaded.
* * private static set<loadimagetask> taskcollection;
Direct sub-layout under/** * Myscrollview.
* * private static View scrolllayout;
/** * Myscrollview The height of the layout.
* * private static int scrollviewheight; /**
* Record the vertical direction of the scroll distance.
* * private static int lastscrolly =-1;
/** * Records all the images on the interface to allow you to control the release of the picture at any time.
* Private list<imageview> Imageviewlist = new arraylist<imageview> ();
/** * The judgment of image visibility check in handler and the operation of loading more pictures. * * private static Handler Handler = new Handler () {public void Handlemessage (Android.os.Message msg) {Myscrollvi
EW Myscrollview = (myscrollview) msg.obj;
int scrolly = myscrollview.getscrolly (); If the current scroll position is the same as last time, it indicates that the scrolling if (scrolly = lastscrolly) {////When the bottom of the scroll is not currently available, and that the next page of the picture is loaded, if (Scrollviewheight +
scrolly >= scrolllayout.getheight () && taskcollection.isempty ()) {myscrollview.loadmoreimages ();
} myscrollview.checkvisibility ();
else {lastscrolly = scrolly;
Message message = new Message ();
Message.obj = Myscrollview;
After 5 milliseconds again the scrolling position is judged handler.sendmessagedelayed (message, 5);
}
};
};
The/** * Myscrollview constructor. * * @param context * @param attrs/public Myscrollview (COntext context, AttributeSet attrs) {Super (context, attrs);
Imageloader = Imageloader.getinstance ();
Taskcollection = new hashset<loadimagetask> ();
Setontouchlistener (this); /** * Carries out some critical initialization operations, obtains the height of the Myscrollview, and obtains the width of the first column.
And here you start loading the first page of the picture. * * @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 ();
}/** * listens to the user's touchscreen events and begins scrolling detection if the user's finger leaves the screen.
* * @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;
/** * Starts loading the next page of the picture, each image will open an asynchronous thread to download.
*/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 (), "Loading ...", 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 (), "No More Pictures", Toast.length_short). Show ();
} else {Toast.maketext (GetContext (), "SD card not Found", Toast.length_short). Show ();
}/** * traverses each picture in the imageviewlist, checks the visibility of the picture, and replaces the picture with an empty image if the picture is already out of the visible range of the screen. */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 = (St
Ring) 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);
}}/** * To determine if the phone has an SD card.
* * @return has SD card returns True, no return false.
* * Private Boolean Hassdcard () {return Environment.MEDIA_MOUNTED.equals (environment. Getexternalstoragestate ());
/** * The task of downloading pictures asynchronously.
* * @author Guolin/class Loadimagetask extends Asynctask<string, Void, bitmap> {/** * image URL Address * * Private String MImAgeurl;
/** * can be reused ImageView * * Private ImageView Mimageview; Public Loadimagetask () {}/** * incoming reusable imageview into * * @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); /** * Loads the picture according to the incoming URL.
If this image already exists in the SD card, read it directly from the SD card, otherwise it will be downloaded from the network.
* * @param imageUrl * Image URL address * @return loaded into memory picture. * * 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 (), COLUMNW
Idth);
if (bitmap!= null) {Imageloader.addbitmaptomemorycache (imageUrl, bitmap);
return bitmap;
} return null;
/** * Add a picture to ImageView * * @param bitmap * @param imagewidth * Picture width * @param imageheight * The height of the picture/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); }/** * Find a column that should be added to the picture at this time.
The principle is to judge the height of the three columns, and the smallest column in the current height is the one that should be added. * * @param imageview * @param imageheight * @return should add a list of pictures/private LinearLayout Findcolumntoadd (ImageView ImageView, int imageheight) {if (firstcolumnheight <= secondcolumnheight) {if (Firstcolumnheight <= ThirdCol
Umnheight) {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;
}/** * Download the picture to the SD card cache.
* * @param imageUrl * image URL address.
* * 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 (), CO
Lumnwidth);
if (bitmap!= null) {Imageloader.addbitmaptomemorycache (imageUrl, bitmap);
/** * Gets the local storage path for the picture.
* * @param imageUrl * image URL address.
* @return The local storage path for the picture.
* * 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 is to realize the waterfall flow Photo Wall Core class, here I would like to highlight to you. First it inherits from ScrollView, allowing the user to scroll through more pictures. This provides a loadmoreimages () method that is specifically used to load the next page of the picture, so in the OnLayout () method We call this method first to initialize the first page of the picture. Then in the Ontouch method, whenever a finger is heard to leave the screen, a handler is used to determine the current ScrollView scrolling state, and if it is found to have scrolled to the bottom, the loadmoreimages is called again () method to load a picture of the next page.
Then we'll take a look at the internal details of the Loadmoreimages () method. In this method, a loop is used to load each picture on the page, and a loadimagetask is opened each time to load the picture asynchronously. Then in the Loadimagetask, first of all will check the image is not already in the SD card, if it does not exist, download from the network, and then put this picture stored in the LRUCache. Then the picture is compressed in a certain proportion, and find the smallest column of the current height, the compressed image added to it.
In addition, in order to ensure that pictures on the wall are properly recycled, a method of visibility checking, the checkvisibility () method, is also added. The central idea of this approach is to examine all the pictures on the wall of the current photo, to determine which ones are visible and which are not. You can then replace the invisible pictures with an empty image, which guarantees that the program will never occupy too much memory. When these pictures become visible again, only need to remove them from the LRUCache again. If a picture has been removed from the LRUCache, a loadimagetask is turned on to reload the image into memory.
Then open or create a new activity_main.xml, and set the waterfall flow layout in it, as follows:
<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" Horizontal "> <linearlayout android:id=" @+id/first_column "android:layout_width=" 0DP "Android:layout_heigh t= "Wrap_content" android:layout_weight= "1" android:orientation= "vertical" > </LinearLayout> <linearl Ayout android:id= "@+id/second_column" android:layout_width= "0DP" android:layout_height= "Wrap_content" Android:layo ut_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:orientat ion= "Vertical" > </LinearLayout>
</LinearLayout> </com.example.photowallfallsdemo.MyScrollView>
As you can see, here we use the Myscrollview as the root layout, and then put a direct sub layout inside it linearlayout to count the height of the current sliding layout. Then you add three equal-width linearlayout to the first column, second column, and third column in this layout, so that you can dynamically add pictures to these three linearlayout in Myscrollview.
Finally, because we use the functions of network and SD card storage, we also need to add the following permissions in Androidmanifest.xml:
<uses-permission android:name= "Android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission Android:name= "Android.permission.INTERNET"/>
So all of our coding work is done and now we can try to run it as shown in the following image:
Waterfall flow pattern of the photo wall is really beautiful, and because we have a very good resource release mechanism, no matter how many pictures you add to the photo wall, the program occupies the memory will always remain within a reasonable range. In the next article, I will take you to further improve the program, add click to see the larger image, as well as multi-touch zoom function, feel interested friends please continue to read Android Multi-Touch technology, free to zoom and move the image.
SOURCE Download: Http://xiazai.jb51.net/201610/yuanma/AndroidPhotoWallFalls (jb51.net). rar
The above is the entire content of this article, I hope to help you learn, but also hope that we support the cloud habitat community.