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();
using (Bitmap bitmapToDisplay =
LoadScaledDownBitmapForDisplay (options, 500, 300)) {
ivImageView.SetImageBitmap(bitmapToDisplay);
}
return root;
}
public Bitmap LoadScaledDownBitmapForDisplay
(BitmapFactory.Options options,int reqWidth, int reqHeight)
{
options.InSampleSize =
CalculateInSampleSize(options, reqWidth, reqHeight);
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeResource(Resources, itemData, options);
}
public BitmapFactory.Options GetBitmapOptionsOfImage()
{
BitmapFactory.Options options = new BitmapFactory.Options
{
InJustDecodeBounds = true,
InPurgeable=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()
{
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 from Android.App.Fragment
and then override the OnCreateView
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 method LoadScaledDownBitmapForDisplay
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 the BitmapFactory.Options
class. Setting the InJustDecodeBounds
property to true
while decoding avoids memory allocation, returning null
for the bitmap object but setting OutWidth
, OutHeight
and OutMimeType
. 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 by Fragment.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 bitmap ivImageView.SetImageBitmap (null);
which helps in releasing the memory of that fragment when it is no longer displayed. Also GC.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;
protected IPageIndicator _indicator;
bool Continue = false;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
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);
_adapter = new FragStateSupport(SupportFragmentManager,itemData);
_pager = FindViewById<viewpager>(Resource.Id.pager);
_pager.Adapter = _adapter;
_indicator = FindViewById<circlepageindicator>(Resource.Id.indicator);
_indicator.SetViewPager(_pager);
}
FragmentActivity
: The above activity class is inherited from the fragment activity class. Using FragmentActivity
, you can easily build tab and swap format. For each tab, you can use different Fragment (Fragments are reusable). So for any FragmentActivity
, you can reuse the same Fragment. FragStateSupport
: The above activity implements a FragStateSupport
adapter class and sets the list of images to be displayed in the fragment. Here FragStateSupport
class is inherited from the FragmentStatePagerAdapter
class. FragmentStatePagerAdapter
: This is used to make our TestFragment
class to be efficient and only create new Fragment instances when necessary. In order to tell our ViewPager
which pages it should show, we have implemented a FragmentStatePagerAdapter
. 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: The FragStateSupport
class is not shown in this article which is a very basic class implementing FragmentStatePagerAdapter
. 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);
}
Thread.Sleep (2000);
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
I am a professional software developer/Analyst programmer with over 4 years of commercial development experience, focusing on the design, development and testing of software solutions using Microsoft technologies. I have been learning android using Xamarin and visual studio in my free time.
Outside of work , I love playing cricket, volleyball, swimming and adventure , travelling are my hobbies.