Joris van Laar
Joris van Laar

Reputation: 75

Android ViewPager: recreate single specific fragment

I use a ViewPager with two Fragments: PhotoFragment and GalleryFragment.
If I swipe to the second fragment (GalleryFragment), I always want to recreate that fragment.
But when I swipe back to the first fragment (PhotoFragment), I want that to be pulled from memory.

I tried to implement the solution on this question Update ViewPager dynamically?

I utilize notifyDataSetChanged() and have overridden the getItemPosition() to return POSITION_NONE when swiping to the second fragment.
And when I swipe to the second fragment, this gets recreated correctly. But when I swipe back to the first fragment, I notice that this has also been recreated.

I guess returning POSITION_NONE results in the removal and recreating of all fragments in the adapter. I'm a beginner Android developer and have no clue how I could just recreate the second fragment only.

MainActivity with ViewPager:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final ViewPager viewPager = findViewById(R.id.viewpager);
        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);

        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int i, float v, int i1) {
            }

            @Override
            public void onPageSelected(int i) {
                if (i == 1) {
                    viewPager.getAdapter().notifyDataSetChanged();
                }
            }

            @Override
            public void onPageScrollStateChanged(int i) {
            }
        });

        TabLayout tabLayout = findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(viewPager);
    }
}

Adapter:

public class ViewPagerAdapter extends FragmentStatePagerAdapter {

    public ViewPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public int getCount() {
        return 2;
    }

    @Override
    public Fragment getItem(int i) {
        switch (i) {
            case 0:
                return new PhotoFragment();
            case 1:
                return new GalleryFragment();
            default:
                return null;
        }
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        switch (position) {
            case 0:
                return "Photo";
            case 1:
                return "Gallery";
            default:
                return null;
        }
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }
}

PhotoFragment:

public class PhotoFragment extends Fragment {

    private static final int CAMERA_REQUEST_CODE = 0;
    private static final int GALLERY_REQUEST_CODE = 1;
    private ImageView mCapturedImageView;
    private String mCacheFileLocation;
    private File mCacheFolder;
    private File mGalleryFolder;
    private String mImageFileLocation;
    private TextView mMonthText;
    private FrameLayout stickerFrameLayout;
    private static final String TAG = "PhotoFragment";

    public PhotoFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View rootView = inflater.inflate(R.layout.fragment_photo, container, false);

        mMonthText = rootView.findViewById(R.id.monthText);
        mCapturedImageView = rootView.findViewById(R.id.capturedImageView);
        ImageButton mCameraButton = rootView.findViewById(R.id.cameraButton);
        ImageButton mSaveButton = rootView.findViewById(R.id.saveButton);
        stickerFrameLayout = rootView.findViewById(R.id.stickerFrameLayout);
        ImageButton mRainbowButton = rootView.findViewById(R.id.buttonRainbow);
        ImageButton mDancerButton = rootView.findViewById(R.id.buttonDancer);
        ImageButton mGlassesButton = rootView.findViewById(R.id.buttonGlasses);
        ImageButton mHeartButton = rootView.findViewById(R.id.buttonHeart);
        ImageButton mCrownButton = rootView.findViewById(R.id.buttonCrown);
        ImageButton mJorisButton = rootView.findViewById(R.id.buttonJoris);

        mMonthText.setVisibility(View.INVISIBLE);
        createImageFolders();

        View.OnClickListener stickerListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addSticker(v.getId());
            }
        };
        mRainbowButton.setOnClickListener(stickerListener);
        mDancerButton.setOnClickListener(stickerListener);
        mGlassesButton.setOnClickListener(stickerListener);
        mHeartButton.setOnClickListener(stickerListener);
        mCrownButton.setOnClickListener(stickerListener);
        mJorisButton.setOnClickListener(stickerListener);

        mCameraButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showPictureDialog();
            }
        });

        mSaveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                saveImage();
            }
        });

        return rootView;
    }

    private void showPictureDialog() {
        AlertDialog.Builder pictureDialog = new AlertDialog.Builder(getActivity());
        pictureDialog.setTitle("Select Action");
        String[] pictureDialogItems = {"Select photo from device", "Capture photo with camera"};
        pictureDialog.setItems(pictureDialogItems, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                switch (which) {
                    case 0:
                        pickImage();
                        break;
                    case 1:
                        takePhoto();
                        break;
                }
            }
        });
        pictureDialog.show();
    }

    private void pickImage() {
        Intent pickImageIntent = new Intent(Intent.ACTION_PICK);
        pickImageIntent.setType("image/*");
        String[] mimeTypes = {"image/jpeg", "image/png"};
        pickImageIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
        pickImageIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
        startActivityForResult(pickImageIntent, GALLERY_REQUEST_CODE);
        mMonthText.setVisibility(View.INVISIBLE);
    }

    private void takePhoto() {
        Intent callCameraApplicationIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (callCameraApplicationIntent.resolveActivity(getActivity().getPackageManager()) != null) {
            File file = null;
            try {
                file = createCacheFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (file != null) {
                Uri photoURI = FileProvider.getUriForFile(getActivity(), "com.jorisvanlaar.employeeofthemonth.fileprovider", file);
                callCameraApplicationIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                startActivityForResult(callCameraApplicationIntent, CAMERA_REQUEST_CODE);
                mMonthText.setVisibility(View.INVISIBLE);
            }
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == getActivity().RESULT_OK) {
            switch (requestCode) {
                case GALLERY_REQUEST_CODE:
                    Bitmap bitmap = null;
                    if (data.getData() != null) {
                        try {
                            bitmap = BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(data.getData()));
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        writeBitmapToFile(bitmap, createCacheFile());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    rotateImage(reduceImageSize());
                    break;

                case CAMERA_REQUEST_CODE:
                    rotateImage(reduceImageSize());
                    break;
            }
        }
    }

    private void createImageFolders() {
        File storageDirectory = getActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        mCacheFolder = new File(storageDirectory, "cached files");
        if (!mCacheFolder.exists()) {
            mCacheFolder.mkdirs();
        }
        mGalleryFolder = new File(storageDirectory, "Image Gallery");
        if (!mGalleryFolder.exists()) {
            mGalleryFolder.mkdirs();
        }
    }

    @SuppressLint("SimpleDateFormat")
    private File createCacheFile() throws IOException {
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String cacheFileName = "IMG_" + timeStamp + "_";
        File image = File.createTempFile(cacheFileName, ".jpg", mCacheFolder);
        mCacheFileLocation = image.getAbsolutePath();
        return image;
    }

    @SuppressLint("SimpleDateFormat")
    private File createImageFile() {
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "EMPL_" + timeStamp + "_.jpg";
        File image = new File(mGalleryFolder, imageFileName);
        mImageFileLocation = image.getAbsolutePath();
        return image;
    }

    private void writeBitmapToFile(Bitmap bitmap, File destination) {
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(destination);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
        } catch (Exception ex) {
            Log.i(TAG, "Error writing bitmap to file");
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    private Bitmap reduceImageSize() {
        int targetImageViewWidth = mCapturedImageView.getWidth();
        int targetImageViewHeight = mCapturedImageView.getHeight();

        BitmapFactory.Options bmOptions = new BitmapFactory.Options();
        bmOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(mCacheFileLocation, bmOptions);
        int cameraImageWidth = bmOptions.outWidth;
        int cameraImageHeight = bmOptions.outHeight;

        int scaleFactor = Math.min(cameraImageWidth / targetImageViewWidth, cameraImageHeight / targetImageViewHeight);

        bmOptions.inJustDecodeBounds = false;
        bmOptions.inSampleSize = scaleFactor;

        Bitmap photoReducedSizeBitmap = BitmapFactory.decodeFile(mCacheFileLocation, bmOptions);
        return photoReducedSizeBitmap;
    }

    private void rotateImage(Bitmap bitmap) {
        ExifInterface exifInterface = null;
        try {
            exifInterface = new ExifInterface(mCacheFileLocation);
        } catch (IOException e) {
            e.printStackTrace();
        }
        int orientation = 0;
        if (exifInterface != null) {
            orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
        }
        Matrix matrix = new Matrix();
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                matrix.setRotate(90);
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                matrix.setRotate(180);
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                matrix.setRotate(270);
                break;
            default:
        }
        Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        mCapturedImageView.setImageBitmap(rotatedBitmap);
    }

    @SuppressLint("SetTextI18n")
    private void saveImage() {
        Calendar calendar = Calendar.getInstance();
        int currentMonth = calendar.get(Calendar.MONTH);
        MonthCollection monthCollection = new MonthCollection();
        mMonthText.setText("Employee of " + monthCollection.getMonth(currentMonth));
        mMonthText.setVisibility(View.VISIBLE);

        Bitmap bitmap = Bitmap.createBitmap(stickerFrameLayout.getWidth(), stickerFrameLayout.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        stickerFrameLayout.draw(canvas);

        writeBitmapToFile(bitmap, createImageFile());
        Toast.makeText(getActivity(), "Image saved!", Toast.LENGTH_SHORT).show();
    }

    private void addSticker(int id) {
        StickerImageView sticker = new StickerImageView(getActivity());
        switch (id) {
            case R.id.buttonRainbow:
                sticker.setImageDrawable(getResources().getDrawable(R.drawable.sticker_rainbow));
                break;
            case R.id.buttonDancer:
                sticker.setImageDrawable(getResources().getDrawable(R.drawable.sticker_dancer));
                break;
            case R.id.buttonGlasses:
                sticker.setImageDrawable(getResources().getDrawable(R.drawable.sticker_sunglasses));
                break;
            case R.id.buttonHeart:
                sticker.setImageDrawable(getResources().getDrawable(R.drawable.sticker_heart));
                break;
            case R.id.buttonCrown:
                sticker.setImageDrawable(getResources().getDrawable(R.drawable.sticker_crown));
                break;
            case R.id.buttonJoris:
                sticker.setImageDrawable(getResources().getDrawable(R.drawable.sticker_joris));
                break;
            default:
                throw new RuntimeException("Unknown button ID");
        }
        stickerFrameLayout.addView(sticker);
    }
}

GalleryFragment:

public class GalleryFragment extends Fragment {

    private RecyclerView mRecyclerView;
    private File mGalleryFolder;
    private static int mColumnCount = 3;
    private static int mImageWidth;
    private static int mImageHeight;

    public GalleryFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View rootView = inflater.inflate(R.layout.fragment_gallery, container, false);

        mRecyclerView = rootView.findViewById(R.id.galleryRecyclerView);
        mGalleryFolder = getActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES + "/Image Gallery");

        DisplayMetrics displayMetrics = new DisplayMetrics();
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        mImageWidth = displayMetrics.widthPixels / mColumnCount;
        mImageHeight = mImageWidth * 4 / 3;

        GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), mColumnCount);
        mRecyclerView.setLayoutManager(layoutManager);

        File[] sortedGalleryFolder = sortFilesToLatest(mGalleryFolder);
        RecyclerView.Adapter imageAdapter = new ImageAdapter(sortedGalleryFolder, mImageWidth, mImageHeight);
        mRecyclerView.setAdapter(imageAdapter);

        return rootView;
    }

    private File[] sortFilesToLatest(File imageDirectory) {
        File[] files = imageDirectory.listFiles();
        Arrays.sort(files, new Comparator<File>() {                                     
            @Override
            public int compare(File o1, File o2) {
                return Long.valueOf(o2.lastModified()).compareTo(o1.lastModified());
            }                                                                           
        });
        return files;
    }
}

Upvotes: 1

Views: 443

Answers (3)

ssynhtn
ssynhtn

Reputation: 1377

The first fragment is recreated because when you call notifyDataSetChanged() it affects both fragments, not only the i == 1 fragment

The solution based on your current state is this

@Override
public int getItemPosition(@NonNull Object object) {
    if (object instanceof PhotoFragment) {
        return POSITION_UNCHANGED;
    }
    return POSITION_NONE;
}

However this is not optimal as the scroll is sluggish, you could try to use a child fragment to achieve what you want

Upvotes: 1

twenk11k
twenk11k

Reputation: 577

Use viewpager.setOffscreenPageLimit(1);

This will set the number of pages that should be retained to side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.

Upvotes: 0

cesarmarch
cesarmarch

Reputation: 637

You can store your first fragment in a variable and reuse it and create the second one each time. Something like :

@Override
public Fragment getItem(int i) {
    switch (i) {
        case 0:
            if(photoFragment == null) {
               photoFragment = new PhotoFragment();
            }
            return photoFragment;
        case 1:
            return new GalleryFragment();
        default:
            return null;
    }
}

Upvotes: 0

Related Questions