Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Xamarin Blueprints

You're reading from   Xamarin Blueprints Leverage the power of Xamarin to create stunning cross-platform and native apps

Arrow left icon
Product type Paperback
Published in Sep 2016
Publisher Packt
ISBN-13 9781785887444
Length 516 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Michael Williams Michael Williams
Author Profile Icon Michael Williams
Michael Williams
Arrow right icon
View More author details
Toc

Table of Contents (9) Chapters Close

Preface 1. Building a Gallery Application FREE CHAPTER 2. Building a SpeechTalk Application 3. Building a GPS Locator Application 4. Building an Audio Player Application 5. Building a Stocklist Application 6. Building a Chat Application 7. Building a File Storage Application 8. Building a Camera Application

Custom row appearance

Let's get back to the ListAdapter implementation and design our ListView row appearance. Open the Resources | Layout folder, create a new .xml file for the cell appearance, call it CustomCell.xml, and copy in the following XML code:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="horizontal" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:weightSum="4"> 
    <LinearLayout 
        android:orientation="vertical" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" 
        android:layout_weight="1"> 
        <ImageView 
            android:id="@+id/image" 
            android:layout_width="100dp" 
            android:layout_height="100dp" 
            android:adjustViewBounds="true" /> 
    </LinearLayout> 
    <LinearLayout 
        android:orientation="vertical" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" 
        android:layout_weight="3" 
        android:weightSum="2"> 
        <TextView 
            android:id="@+id/title" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" /> 
        <TextView 
            android:id="@+id/date" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" /> 
    </LinearLayout> 
</LinearLayout> 

We are creating the same layout as the custom cell made for iOS, but in Android we will use the ImageView and TextView objects. Now that we have our custom cell, we can implement the the GetView function. The GetView function is exactly like the GetCell function in the preceding UITableSource implementation. Open up the ListAdapter.cs file and continue with the list adapter implementation:

public class ListAdapter : BaseAdapter 
    { 
        private List<GalleryItem> _items; 
        private Activity _context; 
 
        public ListAdapter(Activity context) : base() 
        { 
            _context = context; 
            _items = new List<GalleryItem>(); 
        } 
 
        public override Java.Lang.Object GetItem (int position) 
        { 
            return null; 
        } 
 
        public override long GetItemId(int position) 
        { 
            return position; 
        } 
 
        public override int Count 
        { 
            get 
            { 
                return items.Count;  
            } 
        } 
} 

We override the Count property and functions GetItemId and GetItem, to return the number of gallery items in our list. These override functions are exactly the same as the overrides in Java for any BaseAdapter inherited class. Now for the GetView function:

public override View GetView(int position, View convertView, ViewGroup parent) 
        { 
            View view = convertView; // re-use an existing view, if one is available 
 
            if (view == null) 
            { 
                // otherwise create a new one 
                view = context.LayoutInflater.Inflate(Resource.Layout.CustomCell, null); 
            } 
 
            // set image 
            var imageView = view.FindViewById<ImageView> (Resource.Id.image); 
            BitmapHelpers.CreateBitmap (imageView, _items [position].ImageData); 
 
            // set labels 
            var titleTextView = view.FindViewById<TextView> (Resource.Id.title); 
            titleTextView.Text = _items[position].Title; 
            var dateTextView = view.FindViewById<TextView> (Resource.Id.date); 
            dateTextView.Text = _items[position].Date; 
 
            return view; 
        } 
 
        private async void createBitmap(ImageView imageView, byte[] imageData) 
        { 
            try 
            { 
                if (imageData != null)  
                { 
                    var bm = await BitmapFactory.DecodeByteArrayAsync(imageData, 0, imageData.Length); 
                    if (bm != null)  
                    { 
                        imageView.SetImageBitmap(bm); 
                    } 
                } 
            } 
            catch (Exception e)  
            { 
                Console.WriteLine ("Bitmap creation failed: " + e); 
            } 
        } 

Notice in the GetView function we are using the CustomCell layout for each row; we also have a private method for creating our bitmaps from each model's byte array.

If we have a look at the current implementation, what do we notice here?

We are creating a bitmap every time the cell requires this data again for the view; is this efficient? No, we should be reusing bitmaps and memory as much as possible.

This tends to be a common issue with Android ListView.

What is the most memory efficient way to reuse bitmaps across hundreds of items in a ListView while scrolling and staying smooth as we move down the list at various speeds? How can we tackle this problem? Let's have a look at how we can approach this problem.

Firstly, we need to implement an object called ImageHandler. This will contain the logic for retrieving byte arrays from all gallery images on an Android device. Create a new file, name it ImageHandler, and start importing these namespaces:

namespace Gallery.Droid 
{ 
    using System; 
    using System.Collections.Generic; 
 
    using Android.Database; 
    using Android.Content; 
    using Android.Provider; 
 
    using Gallery.Shared; 
 
    public static class ImageHandler 
    { 
    } 
} 

This class will include a function, GetFiles, which will create gallery items based upon the items pulled from any device's gallery using the ContentResolver interface:

public static IEnumerable<GalleryItem> GetFiles(Context context) 
        { 
            ContentResolver cr = context.ContentResolver; 
 
            string[] columns = new string[]  
            { 
                MediaStore.Images.ImageColumns.Id, 
                MediaStore.Images.ImageColumns.Title, 
                MediaStore.Images.ImageColumns.Data, 
                MediaStore.Images.ImageColumns.DateAdded, 
                MediaStore.Images.ImageColumns.MimeType, 
                MediaStore.Images.ImageColumns.Size, 
            }; 
             
            var cursor = cr.Query(MediaStore.Images.Media.ExternalContentUri, columns, null, null, null); 
 
            int columnIndex = cursor.GetColumnIndex(columns[2]); 
 
            int index = 0; 
 
            // create max 100 items 
            while (cursor.MoveToNext () && index < 100)  
            { 
                index++; 
 
                var url = cursor.GetString(columnIndex); 
 
                var imageData = createCompressedImageDataFromBitmap (url); 
 
                yield return new GalleryItem ()  
                { 
                    Title = cursor.GetString(1), 
                    Date = cursor.GetString(3), 
                    ImageData = imageData, 
                    ImageUri = url, 
                }; 
            } 
        } 

Using ContentResolver (used to access the content model), we resolve URIs to specific content providers. A content provider provides queries to content, in our case image files. We simply create an access query off the main context's ContentResolver instance, and we provide an array of columns for the query to retrieve (for example, file titles, file data, file size, and so on). The first parameter is as follows:

"MediaStore.Images.Media.ExternalContentUri" 

This is used for retrieving the URI to each piece of content returned from the query. Finally, we now have a cursor to iterate through, exactly like an Enumerable, which will loop to the end until there are no more items, and for each iteration we pull the data and URI columns and create a new GalleryItem. You will notice a little trick here with the yield keyword: if we call this function, it will actually return the entire Enumerable from start to finish. Calling the function starts for each-ing over the object; the function is called again until it yields. In the return from calling this function, we get an Enumerable of all the items retrieved from the query as gallery items with image information and local URI.

You have been reading a chapter from
Xamarin Blueprints
Published in: Sep 2016
Publisher: Packt
ISBN-13: 9781785887444
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image