I am creating an app, in which one of its activities is used to display images with captions. Those images are displayed using a ViewPager (each image in one page).
I have no problem with creating the images, and displaying them with their captions. However, I am having a problem when I rotate the device. Upon device rotation, the images are deleted, however the captions are retains. I think I know the reason for that (as explained after the code snippets below), but I am unable to solve it, and hence I am asking this question.
First, here is my gallery_item.xml
file that is used to display each image:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView android:id="@+id/gallery_image"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:scaleType="fitCenter"
android:background="@color/black" />
<TextView android:id="@+id/gallery_caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="right"
android:textSize="20sp"
android:paddingStart="@dimen/activity_horizontal_margin"
android:paddingEnd="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:textColor="@color/white"
android:background="@color/transparent_black_percent_50" />
</FrameLayout>
Then the activity code GalleryItemPagerActivity.java
is below
public class GalleryItemPagerActivity extends Activity {
private ViewPager mViewPager;
private NewsItem mNewsItem;
private int mNewsItemId;
ImageDownloaderThread<ImageView> mImageDownloaderThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewPager = new ViewPager(this);
mViewPager.setId(R.id.galleryItemViewPager);
setContentView(mViewPager);
// Create a thread that will be run in the background to download images from the web for each URL
//
// IMPORTANT NOTE:
// We are downloading the image in the activity instead of the fragment because we are use a pager activity
// ... and pager activities must have the adapter in the activity rather than the fragment, which causes us to download the image here
// ... if we try to download in the fragment, then we will have a very complicated code that will cause too many memory leaks.
mImageDownloaderThread = new ImageDownloaderThread<ImageView>(new Handler());
// Prepare listener to update UI when an image is downloaded
mImageDownloaderThread.setListener(new ImageDownloaderThread.Listener() {
// This method is used to update the UI when the image ready
public void onImageDownloaded(Bitmap image, String url, int pos, int download_image_type) {
// First, get the fragment by position
GalleryItemFragment fragment = ((GalleryItemFragmentStatePagerAdapter)mViewPager.getAdapter()).getFragment(pos);
// If the fragment exists
if (fragment != null) {
// Update the image
ImageView imageView = (ImageView) fragment.getView().findViewById(R.id.gallery_image);
imageView.setImageBitmap(image);
}
}
});
// Start the background thread
mImageDownloaderThread.start();
// Prepare the looper for the background thread
mImageDownloaderThread.getLooper();
mNewsItemId = getIntent().getIntExtra(GalleryItemFragment.EXTRA_NEWS_ITEM_ID, 0);
mNewsItem = NewsItems.get(this).getNewsItem(mNewsItemId);
FragmentManager fm = getFragmentManager();
GalleryItemFragmentStatePagerAdapter adapter = new GalleryItemFragmentStatePagerAdapter(fm);
mViewPager.setAdapter(adapter);
}
// Destroy the background thread when we exit the app, otherwise it will stay forever
@Override
public void onDestroy() {
super.onDestroy();
mImageDownloaderThread.quit();
}
// I am subclassing FragmentStatePagerAdapter so I can add the method getFragment()
// ... This method allows me to get the displayed fragment by position, and if it does not exist, a null is returned.
private class GalleryItemFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
// A map to track current fragments.
private Map<Integer, GalleryItemFragment> mFragmentReferenceMap = new HashMap<Integer, GalleryItemFragment>();
// Constructor
public GalleryItemFragmentStatePagerAdapter(FragmentManager fm) {
super(fm);
}
// Implementation of get count.
@Override
public int getCount() {
// The number of images to download is the number of our fragments
int count = mNewsItem.getImages().size();
return count;
}
// Fragment to display, and download the main image and author avatar
@Override
public Fragment getItem(int pos) {
// Download this image
String imageUrl = mNewsItem.getImages().get(pos).getUrl();
// Initiate a request to download the image at the background thread
mImageDownloaderThread.queueImage(imageUrl, pos, ImageDownloaderThread.DOWNLOAD_IMAGE_TYPE_MAIN);
// Get the fragment
GalleryItemFragment fragment = GalleryItemFragment.newInstance(mNewsItemId, pos);
// Add the fragment to our map
mFragmentReferenceMap.put(Integer.valueOf(pos), fragment);
return fragment;
}
// When a fragment is deleted, we have to remove it from the map
@Override
public void destroyItem(ViewGroup container, int pos, Object object) {
mFragmentReferenceMap.remove(Integer.valueOf(pos));
// Remove the fragment from the map
mFragmentReferenceMap.remove(Integer.valueOf(pos));
}
// Get the fragment by position (returns null if fragment does not exist)
public GalleryItemFragment getFragment(int key) {
return mFragmentReferenceMap.get(Integer.valueOf(key));
}
}
}
And the code for the fragment is right at GalleryItemFragment.java
public class GalleryItemFragment extends Fragment {
public static final String EXTRA_NEWS_ITEM_ID = "com.myproject.android.news_item_id";
public static final String EXTRA_GALLERY_ITEM_POSITION = "com.myproject.android.gallery_item_position";
private int mNewsItemId;
private int mGalleryItemPosition;
private TextView mCaptionField;
// This is used to set attach arguments to the fragment
public static GalleryItemFragment newInstance (int news_item_id, int gallery_item_position) {
Bundle args = new Bundle();
args.putInt(EXTRA_NEWS_ITEM_ID, news_item_id);
args.putInt(EXTRA_GALLERY_ITEM_POSITION, gallery_item_position);
GalleryItemFragment fragment = new GalleryItemFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNewsItemId = getArguments().getInt(EXTRA_NEWS_ITEM_ID);
mGalleryItemPosition = getArguments().getInt(EXTRA_GALLERY_ITEM_POSITION);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
// inflate our xml file first
View v = inflater.inflate(R.layout.gallery_item, parent, false);
mCaptionField = (TextView)v.findViewById(R.id.gallery_caption);
mCaptionField.setText(NewsItems.get(getActivity()).getNewsItem(mNewsItemId).getImages().get(mGalleryItemPosition).getCaption());
return v;
}
}
Now to the problem.
As explained earlier, when I rotate the device, the activity is destroyed, but the fragment is retained (as explained in the accepted answer here).
Also, you notice that the images in my case are downloaded in the activity rather than the fragments (due to using a pager activity, which requires that its adapter be in the activity rather than the fragment).
So when the activity is destroyed, the images are gone (but the captions are retained because they are part of the retained fragments). Which leads to a black page without images, but only captions.
Now, I read different solutions (such as this one), and I tried to add the following line into AndroidManifest.xml
android:configChanges="orientation|screenSize"
But this did not help much, as the images look 'disproportionate' when sliding left and right after rotating the device, because of the screenSize
property, which retains the screen size, resulting in funny looking images.
Now, I think I should try onSaveInstanceState()
but I am not sure how to do so. How can I save an image and retain it using onSaveInstanceState()
? And how about other variables in my activity class (such as mViewPager
), do I have to save them?
Thanks.