package de.jeisfeld.augendiagnoselib.components; import android.app.Activity; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; import de.jeisfeld.augendiagnoselib.R; import de.jeisfeld.augendiagnoselib.util.imagefile.EyePhotoPair; /** * Array adapter class to display an eye photo pair in a list. */ public abstract class ListPicturesForNameBaseArrayAdapter extends ArrayAdapter<EyePhotoPair> { /** * The cache size. */ private static final int CACHE_SIZE = 25; /** * Keep up to 25 rows in memory before reusing views. */ private final CacheRange mCacheRange = new CacheRange(CACHE_SIZE); // PUBLIC_FIELDS:START /** * A reference to the activity. */ protected final Activity mActivity; /** * The list of eye photo pairs. */ protected EyePhotoPair[] mEyePhotoPairs; // PUBLIC_FIELDS:END /** * Constructor for the adapter. * * @param activity The activity using the adapter. * @param eyePhotoPairs The array of eye photo pairs to be displayed. */ public ListPicturesForNameBaseArrayAdapter(final Activity activity, @NonNull final EyePhotoPair[] eyePhotoPairs) { super(activity, R.layout.text_view_initializing, eyePhotoPairs); this.mActivity = activity; this.mEyePhotoPairs = eyePhotoPairs; } /** * Default adapter to be used by the framework. * * @param context The Context the view is running in. */ public ListPicturesForNameBaseArrayAdapter(final Context context) { super(context, R.layout.adapter_list_pictures_for_name); this.mActivity = (Activity) context; } /** * Abstract method do return the layout to be used. * * @return The layout to be used. */ protected abstract int getLayout(); /** * Abstract method to prepare the image views for selection of pictures. * * @param view The image view to be prepared. */ protected abstract void prepareViewForSelection(EyeImageView view); /* * Fill the display of the view (date and pictures) Details on selection are handled within the * ImageSelectionAndDisplayHandler class */ // OVERRIDABLE @Nullable @Override public View getView(final int position, @Nullable final View convertView, final ViewGroup parent) { View rowView; // Reuse views if they are already created and in the cached range if (convertView != null && mCacheRange.isInRange(position)) { rowView = convertView; } else { rowView = LayoutInflater.from(mActivity).inflate(getLayout(), parent, false); mCacheRange.putIntoRange(position); } final TextView textView = (TextView) rowView.findViewById(R.id.textPictureDate); textView.setText(mEyePhotoPairs[position].getDateDisplayString("dd.MM.yyyy")); // Fill pictures in separate thread, for performance reasons final EyeImageView imageListRight = (EyeImageView) rowView.findViewById(R.id.imageListRight); if (!imageListRight.isInitialized() && mEyePhotoPairs[position].getRightEye() != null) { // Prevent duplicate initialization in case of multiple parallel calls - will happen in dialog imageListRight.setInitialized(); imageListRight.setEyePhoto(mActivity, mEyePhotoPairs[position].getRightEye(), new Runnable() { @Override public void run() { prepareViewForSelection(imageListRight); } }); } final EyeImageView imageListLeft = (EyeImageView) rowView.findViewById(R.id.imageListLeft); if (!imageListLeft.isInitialized() && mEyePhotoPairs[position].getLeftEye() != null) { imageListLeft.setInitialized(); imageListLeft.setEyePhoto(mActivity, mEyePhotoPairs[position].getLeftEye(), new Runnable() { @Override public void run() { prepareViewForSelection(imageListLeft); } }); } return rowView; } /* * After cacheRange.length entries, the views are recycled, i.e. row cacheRange.length is stored in the same view as * row 0. */ @Override public final int getItemViewType(final int position) { return position % mCacheRange.mLength; } @Override public final int getViewTypeCount() { int count = getCount(); if (count == 0) { return 1; } else if (count < mCacheRange.mLength) { return count; } else { return mCacheRange.mLength; } } /** * This is the range of positions for which the images are stored. */ private static final class CacheRange { /** * Length of the cache. */ private final int mLength; /** * Start position of the cache. Moves to ensure that the current pointer is always within the cache. */ private int mStart; /** * Initialize the cache with a given length. * * @param length The length of the cache. */ private CacheRange(final int length) { this.mStart = 0; this.mLength = length; } /** * Check if a given number is within the range of the cache. * * @param n The number to be checked. * @return True if the number is in the cache. */ private boolean isInRange(final int n) { return (mStart <= n) && (n < mStart + mLength); } @NonNull @Override public String toString() { return "[" + mStart + "," + (mStart + mLength - 1) + "]"; } /** * Push a given number into the cache. The start position of the cache is adapted accordingly. * * @param n The number to be pushed. */ private void putIntoRange(final int n) { if (n < mStart) { mStart = n; } else if (n >= mStart + mLength) { mStart = n + 1 - mLength; } } } }