Xamarin Android Image Auto Slider and OOM Issue Handled






4.71/5 (4 votes)
This tip helps you to understand how to implement an auto image scroller and handled OOM out of memory issue in Xamarin Android.
Introduction
This tip will help you to understand how to implement auto image slider (scrolling images) in Xamarin Android. Also it handles growing heap memory by flushing the memory in background when the images are not displayed on UI.
AIM
Background
I was creating an app which has dashboard activity where images have to be scrolling and showing the latest advertisement. After researching on Google, I couldn't manage to find any easy plugin for image scroller and I ended up writing the below code.
Using the Code
Let's get straight into the code. I created an activity which in-turn call the fragment adapter and displays images. Let me show you the whole Fragment Class Code and I'll explain the code inside the class as I go.
public class TestFragment: Fragment
{
private const string KeyContent = "TestFragment:Content";
private string _content = "???";
private int itemData;
private Bitmap myBitmap;
private ImageView ivImageView;
public static TestFragment NewInstance()
{
TestFragment f = new TestFragment ();
return f;
}
public void setImageList(int intData)
{
this.itemData = intData;
}
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if ((savedInstanceState != null)
&& savedInstanceState.ContainsKey(KeyContent))
_content = savedInstanceState.GetString(KeyContent);
}
public override View OnCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState)
{
View root = inflater.Inflate
(Resource.Layout.ViewImageFlipper, container, false);
ivImageView = root.FindViewById<imageview>(Resource.Id.img);
int height = ivImageView.Height;
int width = Resources.DisplayMetrics.WidthPixels;
BitmapFactory.Options options = GetBitmapOptionsOfImage();
//option #1
using (Bitmap bitmapToDisplay =
LoadScaledDownBitmapForDisplay (options, 500, 300)) {
ivImageView.SetImageBitmap(bitmapToDisplay);
}
return root;
}
public Bitmap LoadScaledDownBitmapForDisplay
(BitmapFactory.Options options,int reqWidth, int reqHeight)
{
// Calculate inSampleSize
options.InSampleSize =
CalculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeResource(Resources, itemData, options);
}
public BitmapFactory.Options GetBitmapOptionsOfImage()
{
//Get only the bounds of the bitmap image
BitmapFactory.Options options = new BitmapFactory.Options
{
InJustDecodeBounds = true,
InPurgeable=true,
};
// The result will be null because InJustDecodeBounds == true.
Bitmap result=
BitmapFactory.DecodeResource(Resources, itemData, options);
int imageHeight = options.OutHeight;
int imageWidth = options.OutWidth;
Console.WriteLine(string.Format("Original Size= {0}x{1}",
imageWidth, imageHeight));
return options;
}
public override void OnDestroyView()
{
//When Image Is out of view, Clear the Memory use.
base.OnDestroyView ();
if (ivImageView != null) {
ivImageView.SetImageBitmap (null);
GC.Collect (1);
}
}
}
Fragment
: A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. To create a Fragment, a class must inherit fromAndroid.App.Fragment
and then override theOnCreateView
method.OnCreateView
: The system calls this when it's time for the fragment to draw its user interface for the first time. In the above implementation, the code is trying to decode the bitmap image by calling methodLoadScaledDownBitmapForDisplay
and assigning the image to the image view (ivImageView
).GetBitmapOptionsOfImage
: This method lets you specify decoding options, such as loading a smaller version of the bitmap, via theBitmapFactory.Options
class. Setting theInJustDecodeBounds
property totrue
while decoding avoids memory allocation, returningnull
for the bitmap object but settingOutWidth
,OutHeight
andOutMimeType
. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap.OnDestroyView
: Called when the view previously created byFragment.OnCreateView(LayoutInflater,ViewGroup,ViewGroup)
has been detached from the fragment. This helps in releasing the memory of the object consumed by the previous fragment. In the above implementation, the code is assigning bitmapivImageView.SetImageBitmap (null);
which helps in releasing the memory of that fragment when it is no longer displayed. AlsoGC.Collect(1)
is used to release memory specially in lollipop devices.
Note: You can also make use of any caching library to cache the images if you are downloading the images from the server. The above example has images in its resource folder so I'm not worried about caching images at the moment.
Now let's create an activity which then calls the fragment state pager adapter and sets the image fragments (using TestFragment
class).
[Activity (Label = "AutoImageScroller")]
public class AutoImageScroller : FragmentActivity
{
Button btn;
private List<int> itemData;
private int imageValue;
FragStateSupport _adapter;
ViewPager _pager;
//IpageIndicator is used to show current circles on the images as indicator;
protected IPageIndicator _indicator;
bool Continue = false;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Create your application here
itemData = new List<int> ();
itemData.Add (Resource.Drawable.photo1);
itemData.Add (Resource.Drawable.photo2);
itemData.Add (Resource.Drawable.photo3);
itemData.Add (Resource.Drawable.photo4);
imageValue = 0;
SetContentView(Resource.Layout.simple_circle_viewpager);
//Set up adapter with List of photo ID as item Data
_adapter = new FragStateSupport(SupportFragmentManager,itemData);
//Setup pager reference
_pager = FindViewById<viewpager>(Resource.Id.pager);
_pager.Adapter = _adapter;
//Setup CirclePageIndicator Reference
_indicator = FindViewById<circlepageindicator>(Resource.Id.indicator);
_indicator.SetViewPager(_pager);
}
FragmentActivity
: The above activity class is inherited from the fragment activity class. UsingFragmentActivity
, you can easily build tab and swap format. For each tab, you can use different Fragment (Fragments are reusable). So for anyFragmentActivity
, you can reuse the same Fragment.FragStateSupport
: The above activity implements aFragStateSupport
adapter class and sets the list of images to be displayed in the fragment. HereFragStateSupport
class is inherited from theFragmentStatePagerAdapter
class.FragmentStatePagerAdapter
: This is used to make ourTestFragment
class to be efficient and only create new Fragment instances when necessary. In order to tell ourViewPager
which pages it should show, we have implemented aFragmentStatePagerAdapter
. Each page is a fragment which gets destroyed as soon as it’s not visible to the user, i.e., the user navigates to another one. Due to this behaviour, it consumes much less memory but then doesn’t perform as well as its counterpart (FragmentPagerAdapter
). Hence, it’s perfect in the cases where the number of pages is high or undetermined.
Note: TheFragStateSupport
class is not shown in this article which is a very basic class implementingFragmentStatePagerAdapter
. You can see the implementation of the class in the attached project.
Now, it is time to implement the logic in the activity class which runs on the background thread continuously and keep rotating/changing the image fragment dynamically for a given time interval and updates the UI.
async void HandleClick (object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem (o => RunSlowMethod ());
}
public void RunSlowMethod()
{
while (Continue) {
imageValue = imageValue + 1;
if (imageValue > 3) {
imageValue = 0;
Console.WriteLine("Compute reset to first image : t" + imageValue);
}
//displaying image for 2 seconds
Thread.Sleep (2000);
//Updating the view pager with the next image.
this.RunOnUiThread (() => _pager.SetCurrentItem (imageValue, true));
}
}
The above code is pretty self explanatory, i.e., the code is running on the background thread and updating the viewpager
(_pager
) with the next image value. As soon as it reaches the last images, it resets the imageValue
to start from first.
That's all to implement auto image scroller in the Android app.
Points of Interest
I managed to implement auto image scroller with less amount of code and OOM for bitmap images reaching the heap memory has been reduced. I realize this is a cool tip to implement simple auto image scroller. Why use FragmentStatePagerAdapter
? is explained in this tip when you have undefined number of images/fragments and how to re-use them with less memory consumption.
History
- V1.0: Initial version on October 11, 2015