Merge pull request #3993 from ByteHamster/recycle-viewholders

Recycle ViewHolders throughout the whole app
This commit is contained in:
H. Lehmann 2020-04-02 19:19:40 +02:00 committed by GitHub
commit 5e344baf4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 174 additions and 194 deletions

View File

@ -23,6 +23,7 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar;
@ -74,6 +75,7 @@ public class MainActivity extends CastEnabledActivity {
private ActionBarDrawerToggle drawerToggle;
private LockableBottomSheetBehavior sheetBehavior;
private long lastBackButtonPressTime = 0;
private RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();
@NonNull
public static Intent getIntentToOpenFeed(@NonNull Context context, long feedId) {
@ -89,6 +91,7 @@ public class MainActivity extends CastEnabledActivity {
super.onCreate(savedInstanceState);
StorageUtils.checkStorageAvailability(this);
setContentView(R.layout.main);
recycledViewPool.setMaxRecycledViews(R.id.episode_item_view_holder, 25);
drawerLayout = findViewById(R.id.drawer_layout);
navDrawer = findViewById(R.id.navDrawerFragment);
@ -191,6 +194,10 @@ public class MainActivity extends CastEnabledActivity {
findViewById(R.id.audioplayerFragment).setVisibility(visible ? View.VISIBLE : View.GONE);
}
public RecyclerView.RecycledViewPool getRecycledViewPool() {
return recycledViewPool;
}
public void loadFragment(String tag, Bundle args) {
Log.d(TAG, "loadFragment(tag: " + tag + ", args: " + args + ")");
Fragment fragment;

View File

@ -42,14 +42,25 @@ public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemView
notifyDataSetChanged();
}
@Override
public final int getItemViewType(int position) {
return R.id.episode_item_view_holder;
}
@NonNull
@Override
public EpisodeItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
public final EpisodeItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new EpisodeItemViewHolder(mainActivityRef.get(), parent);
}
@Override
public void onBindViewHolder(EpisodeItemViewHolder holder, int pos) {
public final void onBindViewHolder(EpisodeItemViewHolder holder, int pos) {
// Reset state of recycled views
holder.coverHolder.setVisibility(View.VISIBLE);
holder.dragHandle.setVisibility(View.GONE);
beforeBindViewHolder(holder, pos);
FeedItem item = episodes.get(pos);
holder.bind(item);
holder.itemView.setOnLongClickListener(v -> {
@ -65,9 +76,16 @@ public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemView
}
});
holder.itemView.setOnCreateContextMenuListener(this);
afterBindViewHolder(holder, pos);
holder.hideSeparatorIfNecessary();
}
protected void beforeBindViewHolder(EpisodeItemViewHolder holder, int pos) {
}
protected void afterBindViewHolder(EpisodeItemViewHolder holder, int pos) {
}
/**
* {@link #notifyItemChanged(int)} is final, so we can not override.
* Calling {@link #notifyItemChanged(int)} may bind the item to a new ViewHolder and execute a transition.

View File

@ -35,8 +35,7 @@ public class QueueRecyclerAdapter extends EpisodeItemListAdapter {
@Override
@SuppressLint("ClickableViewAccessibility")
public void onBindViewHolder(EpisodeItemViewHolder holder, int pos) {
super.onBindViewHolder(holder, pos);
protected void afterBindViewHolder(EpisodeItemViewHolder holder, int pos) {
View.OnTouchListener startDragTouchListener = (v1, event) -> {
if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
Log.d(TAG, "startDrag()");
@ -56,7 +55,6 @@ public class QueueRecyclerAdapter extends EpisodeItemListAdapter {
}
holder.isInQueue.setVisibility(View.GONE);
holder.hideSeparatorIfNecessary();
}
@Override

View File

@ -13,9 +13,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
@ -31,6 +28,7 @@ import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.view.EmptyViewHandler;
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -55,7 +53,7 @@ public class CompletedDownloadsFragment extends Fragment {
private List<FeedItem> items = new ArrayList<>();
private CompletedDownloadsListAdapter adapter;
private RecyclerView recyclerView;
private EpisodeItemListRecyclerView recyclerView;
private ProgressBar progressBar;
private Disposable disposable;
private EmptyViewHandler emptyView;
@ -68,10 +66,7 @@ public class CompletedDownloadsFragment extends Fragment {
toolbar.setVisibility(View.GONE);
recyclerView = root.findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
recyclerView.setVisibility(View.GONE);
adapter = new CompletedDownloadsListAdapter((MainActivity) getActivity());
recyclerView.setAdapter(adapter);
@ -215,8 +210,7 @@ public class CompletedDownloadsFragment extends Fragment {
}
@Override
public void onBindViewHolder(EpisodeItemViewHolder holder, int pos) {
super.onBindViewHolder(holder, pos);
public void afterBindViewHolder(EpisodeItemViewHolder holder, int pos) {
DeleteActionButton actionButton = new DeleteActionButton(getItem(pos));
actionButton.configure(holder.secondaryActionButton, holder.secondaryActionIcon, getActivity());
}

View File

@ -1,12 +1,9 @@
package de.danoeh.antennapod.fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import android.util.Log;
@ -20,13 +17,12 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
import de.danoeh.antennapod.core.event.FeedListUpdateEvent;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.event.PlayerStatusEvent;
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
@ -61,18 +57,15 @@ import io.reactivex.schedulers.Schedulers;
public abstract class EpisodesListFragment extends Fragment {
public static final String TAG = "EpisodesListFragment";
private static final String DEFAULT_PREF_NAME = "PrefAllEpisodesFragment";
private static final String PREF_SCROLL_POSITION = "scroll_position";
private static final String PREF_SCROLL_OFFSET = "scroll_offset";
protected static final int EPISODES_PER_PAGE = 150;
private static final int VISIBLE_EPISODES_SCROLL_THRESHOLD = 5;
protected int page = 1;
protected boolean isLoadingMore = false;
protected boolean hasMoreItems = true;
RecyclerView recyclerView;
EpisodeItemListRecyclerView recyclerView;
EpisodeItemListAdapter listAdapter;
ProgressBar progLoading;
View loadingMore;
View loadingMoreView;
EmptyViewHandler emptyView;
@NonNull
@ -81,11 +74,10 @@ public abstract class EpisodesListFragment extends Fragment {
private volatile boolean isUpdatingFeeds;
private boolean isMenuVisible = true;
protected Disposable disposable;
private LinearLayoutManager layoutManager;
protected TextView txtvInformation;
String getPrefName() {
return DEFAULT_PREF_NAME;
return TAG;
}
@Override
@ -105,7 +97,7 @@ public abstract class EpisodesListFragment extends Fragment {
@Override
public void onPause() {
super.onPause();
saveScrollPosition();
recyclerView.saveScrollPosition(getPrefName());
unregisterForContextMenu(recyclerView);
}
@ -118,37 +110,6 @@ public abstract class EpisodesListFragment extends Fragment {
}
}
private void saveScrollPosition() {
int firstItem = layoutManager.findFirstVisibleItemPosition();
View firstItemView = layoutManager.findViewByPosition(firstItem);
float topOffset;
if (firstItemView == null) {
topOffset = 0;
} else {
topOffset = firstItemView.getTop();
}
SharedPreferences prefs = getActivity().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(PREF_SCROLL_POSITION, firstItem);
editor.putFloat(PREF_SCROLL_OFFSET, topOffset);
editor.apply();
}
private void restoreScrollPosition() {
SharedPreferences prefs = getActivity().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE);
int position = prefs.getInt(PREF_SCROLL_POSITION, 0);
float offset = prefs.getFloat(PREF_SCROLL_OFFSET, 0.0f);
if (position > 0 || offset > 0) {
layoutManager.scrollToPositionWithOffset(position, (int) offset);
// restore once, then forget
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(PREF_SCROLL_POSITION, 0);
editor.putFloat(PREF_SCROLL_OFFSET, 0.0f);
editor.apply();
}
}
private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker =
() -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds();
@ -241,12 +202,9 @@ public abstract class EpisodesListFragment extends Fragment {
View root = inflater.inflate(R.layout.all_episodes_fragment, container, false);
txtvInformation = root.findViewById(R.id.txtvInformation);
layoutManager = new LinearLayoutManager(getActivity());
recyclerView = root.findViewById(android.R.id.list);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
recyclerView.setVisibility(View.GONE);
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
setupLoadMoreScrollListener();
RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator();
@ -256,7 +214,7 @@ public abstract class EpisodesListFragment extends Fragment {
progLoading = root.findViewById(R.id.progLoading);
progLoading.setVisibility(View.VISIBLE);
loadingMore = root.findViewById(R.id.loadingMore);
loadingMoreView = root.findViewById(R.id.loadingMore);
emptyView = new EmptyViewHandler(getContext());
emptyView.attachToRecyclerView(recyclerView);
@ -272,33 +230,10 @@ public abstract class EpisodesListFragment extends Fragment {
private void setupLoadMoreScrollListener() {
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
/* Total number of episodes after last load */
private int previousTotalEpisodes = 0;
/* True if loading more episodes is still in progress */
private boolean isLoadingMore = true;
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int deltaX, int deltaY) {
super.onScrolled(recyclerView, deltaX, deltaY);
int visibleEpisodeCount = recyclerView.getChildCount();
int totalEpisodeCount = recyclerView.getLayoutManager().getItemCount();
int firstVisibleEpisode = layoutManager.findFirstVisibleItemPosition();
/* Determine if loading more episodes has finished */
if (isLoadingMore) {
if (totalEpisodeCount > previousTotalEpisodes) {
isLoadingMore = false;
previousTotalEpisodes = totalEpisodeCount;
}
}
/* Determine if the user scrolled to the bottom and loading more episodes is not already in progress */
if (!isLoadingMore && (totalEpisodeCount - visibleEpisodeCount)
<= (firstVisibleEpisode + VISIBLE_EPISODES_SCROLL_THRESHOLD)) {
public void onScrolled(@NonNull RecyclerView view, int deltaX, int deltaY) {
super.onScrolled(view, deltaX, deltaY);
if (!isLoadingMore && hasMoreItems && recyclerView.isScrolledToBottom()) {
/* The end of the list has been reached. Load more data. */
page++;
loadMoreItems();
@ -312,26 +247,35 @@ public abstract class EpisodesListFragment extends Fragment {
if (disposable != null) {
disposable.dispose();
}
loadingMore.setVisibility(View.VISIBLE);
isLoadingMore = true;
loadingMoreView.setVisibility(View.VISIBLE);
disposable = Observable.fromCallable(this::loadMoreData)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
loadingMore.setVisibility(View.GONE);
progLoading.setVisibility(View.GONE);
if (data.size() < EPISODES_PER_PAGE) {
hasMoreItems = false;
}
episodes.addAll(data);
onFragmentLoaded(episodes);
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}, error -> Log.e(TAG, Log.getStackTraceString(error)),
() -> {
recyclerView.post(() -> isLoadingMore = false); // Make sure to not always load 2 pages at once
progLoading.setVisibility(View.GONE);
loadingMoreView.setVisibility(View.GONE);
});
}
protected void onFragmentLoaded(List<FeedItem> episodes) {
boolean restoreScrollPosition = listAdapter.getItemCount() == 0;
if (episodes.size() == 0) {
createRecycleAdapter(recyclerView, emptyView);
} else {
listAdapter.updateItems(episodes);
}
restoreScrollPosition();
if (restoreScrollPosition) {
recyclerView.restoreScrollPosition(getPrefName());
}
if (isMenuVisible && isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) {
requireActivity().invalidateOptionsMenu();
}
@ -431,6 +375,7 @@ public abstract class EpisodesListFragment extends Fragment {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
progLoading.setVisibility(View.GONE);
hasMoreItems = true;
episodes = data;
onFragmentLoaded(episodes);
}, error -> Log.e(TAG, Log.getStackTraceString(error)));

View File

@ -23,7 +23,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
@ -31,7 +30,6 @@ import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.joanzapata.iconify.Iconify;
import com.joanzapata.iconify.widget.IconTextView;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
@ -66,6 +64,7 @@ import de.danoeh.antennapod.dialog.RenameFeedDialog;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
import de.danoeh.antennapod.view.ToolbarIconTintManager;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
import io.reactivex.Observable;
@ -90,7 +89,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
private MoreContentListFooterUtil nextPageLoader;
private ProgressBar progressBar;
private RecyclerView recyclerView;
private EpisodeItemListRecyclerView recyclerView;
private TextView txtvTitle;
private IconTextView txtvFailure;
private ImageView imgvBackground;
@ -144,10 +143,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
recyclerView = root.findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
recyclerView.setVisibility(View.GONE);
progressBar = root.findViewById(R.id.progLoading);
@ -193,16 +189,11 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
});
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int deltaX, int deltaY) {
super.onScrolled(recyclerView, deltaX, deltaY);
int visibleEpisodeCount = recyclerView.getChildCount();
int totalEpisodeCount = recyclerView.getLayoutManager().getItemCount();
int firstVisibleEpisode = layoutManager.findFirstVisibleItemPosition();
boolean isAtBottom = (totalEpisodeCount - visibleEpisodeCount) <= (firstVisibleEpisode + 3);
public void onScrolled(@NonNull RecyclerView view, int deltaX, int deltaY) {
super.onScrolled(view, deltaX, deltaY);
boolean hasMorePages = feed != null && feed.isPaged() && feed.getNextPageLink() != null;
nextPageLoader.getRoot().setVisibility((isAtBottom && hasMorePages) ? View.VISIBLE : View.GONE);
nextPageLoader.getRoot().setVisibility(
(recyclerView.isScrolledToBottom() && hasMorePages) ? View.VISIBLE : View.GONE);
}
});
@ -556,12 +547,9 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
super(mainActivity);
}
@NonNull
@Override
public EpisodeItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
EpisodeItemViewHolder viewHolder = super.onCreateViewHolder(parent, viewType);
viewHolder.coverHolder.setVisibility(View.GONE);
return viewHolder;
protected void beforeBindViewHolder(EpisodeItemViewHolder holder, int pos) {
holder.coverHolder.setVisibility(View.GONE);
}
}
}

View File

@ -8,12 +8,13 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.core.util.playback.Timeline;
import de.danoeh.antennapod.view.ShownotesWebView;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
@ -74,7 +75,7 @@ public class ItemDescriptionFragment extends Fragment {
if (webViewLoader != null) {
webViewLoader.dispose();
}
webViewLoader = Observable.fromCallable(this::loadData)
webViewLoader = Maybe.fromCallable(this::loadData)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
@ -84,10 +85,14 @@ public class ItemDescriptionFragment extends Fragment {
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
@NonNull
@Nullable
private String loadData() {
if (controller.getMedia() != null) {
Timeline timeline = new Timeline(getActivity(), controller.getMedia());
return timeline.processShownotes();
} else {
return null;
}
}
@Override

View File

@ -16,9 +16,6 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
@ -34,6 +31,7 @@ import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.view.EmptyViewHandler;
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -51,7 +49,7 @@ public class PlaybackHistoryFragment extends Fragment {
private List<FeedItem> playbackHistory;
private PlaybackHistoryListAdapter adapter;
private Disposable disposable;
private RecyclerView recyclerView;
private EpisodeItemListRecyclerView recyclerView;
private EmptyViewHandler emptyView;
private ProgressBar progressBar;
@ -71,10 +69,7 @@ public class PlaybackHistoryFragment extends Fragment {
((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
recyclerView = root.findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
recyclerView.setVisibility(View.GONE);
adapter = new PlaybackHistoryListAdapter((MainActivity) getActivity());
recyclerView.setAdapter(adapter);
@ -246,8 +241,7 @@ public class PlaybackHistoryFragment extends Fragment {
}
@Override
public void onBindViewHolder(EpisodeItemViewHolder holder, int pos) {
super.onBindViewHolder(holder, pos);
protected void afterBindViewHolder(EpisodeItemViewHolder holder, int pos) {
// played items shouldn't be transparent for this fragment since, *all* items
// in this fragment will, by definition, be played. So it serves no purpose and can make
// it harder to read.

View File

@ -18,11 +18,9 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import com.google.android.material.snackbar.Snackbar;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.QueueRecyclerAdapter;
@ -49,6 +47,7 @@ import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.view.EmptyViewHandler;
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -71,7 +70,7 @@ public class QueueFragment extends Fragment {
public static final String TAG = "QueueFragment";
private TextView infoBar;
private RecyclerView recyclerView;
private EpisodeItemListRecyclerView recyclerView;
private QueueRecyclerAdapter recyclerAdapter;
private EmptyViewHandler emptyView;
private ProgressBar progLoading;
@ -81,16 +80,12 @@ public class QueueFragment extends Fragment {
private boolean isUpdatingFeeds = false;
private static final String PREFS = "QueueFragment";
private static final String PREF_SCROLL_POSITION = "scroll_position";
private static final String PREF_SCROLL_OFFSET = "scroll_offset";
private static final String PREF_SHOW_LOCK_WARNING = "show_lock_warning";
private Disposable disposable;
private LinearLayoutManager layoutManager;
private ItemTouchHelper itemTouchHelper;
private SharedPreferences prefs;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -112,7 +107,7 @@ public class QueueFragment extends Fragment {
@Override
public void onPause() {
super.onPause();
saveScrollPosition();
recyclerView.saveScrollPosition(QueueFragment.TAG);
}
@Override
@ -159,7 +154,7 @@ public class QueueFragment extends Fragment {
case MOVED:
return;
}
saveScrollPosition();
recyclerView.saveScrollPosition(QueueFragment.TAG);
onFragmentLoaded(false);
}
@ -232,30 +227,6 @@ public class QueueFragment extends Fragment {
}
}
private void saveScrollPosition() {
int firstItem = layoutManager.findFirstVisibleItemPosition();
View firstItemView = layoutManager.findViewByPosition(firstItem);
float topOffset;
if(firstItemView == null) {
topOffset = 0;
} else {
topOffset = firstItemView.getTop();
}
prefs.edit()
.putInt(PREF_SCROLL_POSITION, firstItem)
.putFloat(PREF_SCROLL_OFFSET, topOffset)
.apply();
}
private void restoreScrollPosition() {
int position = prefs.getInt(PREF_SCROLL_POSITION, 0);
float offset = prefs.getFloat(PREF_SCROLL_OFFSET, 0.0f);
if (position > 0 || offset > 0) {
layoutManager.scrollToPositionWithOffset(position, (int) offset);
}
}
private void resetViewState() {
recyclerAdapter = null;
}
@ -480,9 +451,7 @@ public class QueueFragment extends Fragment {
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
registerForContextMenu(recyclerView);
itemTouchHelper = new ItemTouchHelper(
@ -598,7 +567,7 @@ public class QueueFragment extends Fragment {
}
if (restoreScrollPosition) {
restoreScrollPosition();
recyclerView.restoreScrollPosition(QueueFragment.TAG);
}
// we need to refresh the options menu because it sometimes

View File

@ -1,6 +1,5 @@
package de.danoeh.antennapod.fragment;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
@ -18,7 +17,6 @@ import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
@ -35,6 +33,7 @@ import de.danoeh.antennapod.core.storage.FeedSearcher;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.view.EmptyViewHandler;
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -59,8 +58,7 @@ public class SearchFragment extends Fragment {
private Disposable disposable;
private ProgressBar progressBar;
private EmptyViewHandler emptyViewHandler;
private RecyclerView recyclerView;
private RecyclerView recyclerViewFeeds;
private EpisodeItemListRecyclerView recyclerView;
private List<FeedItem> results;
/**
@ -117,15 +115,12 @@ public class SearchFragment extends Fragment {
progressBar = layout.findViewById(R.id.progressBar);
recyclerView = layout.findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
recyclerView.setVisibility(View.GONE);
adapter = new EpisodeItemListAdapter((MainActivity) getActivity());
recyclerView.setAdapter(adapter);
recyclerViewFeeds = layout.findViewById(R.id.recyclerViewFeeds);
RecyclerView recyclerViewFeeds = layout.findViewById(R.id.recyclerViewFeeds);
LinearLayoutManager layoutManagerFeeds = new LinearLayoutManager(getActivity());
layoutManagerFeeds.setOrientation(RecyclerView.HORIZONTAL);
recyclerViewFeeds.setLayoutManager(layoutManagerFeeds);

View File

@ -0,0 +1,73 @@
package de.danoeh.antennapod.view;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.AttributeSet;
import android.view.View;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import io.reactivex.annotations.Nullable;
public class EpisodeItemListRecyclerView extends RecyclerView {
private static final String TAG = "EpisodeItemListRecyclerView";
private static final String PREF_PREFIX_SCROLL_POSITION = "scroll_position_";
private static final String PREF_PREFIX_SCROLL_OFFSET = "scroll_offset_";
private LinearLayoutManager layoutManager;
public EpisodeItemListRecyclerView(Context context) {
super(context);
setup();
}
public EpisodeItemListRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setup();
}
public EpisodeItemListRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setup();
}
private void setup() {
layoutManager = new LinearLayoutManager(getContext());
layoutManager.setRecycleChildrenOnDetach(true);
setLayoutManager(layoutManager);
setHasFixedSize(true);
addItemDecoration(new HorizontalDividerItemDecoration.Builder(getContext()).build());
}
public void saveScrollPosition(String tag) {
int firstItem = layoutManager.findFirstVisibleItemPosition();
View firstItemView = layoutManager.findViewByPosition(firstItem);
float topOffset;
if (firstItemView == null) {
topOffset = 0;
} else {
topOffset = firstItemView.getTop();
}
getContext().getSharedPreferences(TAG, Context.MODE_PRIVATE).edit()
.putInt(PREF_PREFIX_SCROLL_POSITION + tag, firstItem)
.putInt(PREF_PREFIX_SCROLL_OFFSET + tag, (int) topOffset)
.apply();
}
public void restoreScrollPosition(String tag) {
SharedPreferences prefs = getContext().getSharedPreferences(TAG, Context.MODE_PRIVATE);
int position = prefs.getInt(PREF_PREFIX_SCROLL_POSITION + tag, 0);
int offset = prefs.getInt(PREF_PREFIX_SCROLL_OFFSET + tag, 0);
if (position > 0 || offset > 0) {
layoutManager.scrollToPositionWithOffset(position, offset);
}
}
public boolean isScrolledToBottom() {
int visibleEpisodeCount = getChildCount();
int totalEpisodeCount = layoutManager.getItemCount();
int firstVisibleEpisode = layoutManager.findFirstVisibleItemPosition();
return (totalEpisodeCount - visibleEpisodeCount) <= (firstVisibleEpisode + 3);
}
}

View File

@ -15,7 +15,6 @@ import com.joanzapata.iconify.Iconify;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.CoverLoader;
import de.danoeh.antennapod.adapter.QueueRecyclerAdapter;
import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.feed.FeedItem;

View File

@ -17,7 +17,7 @@
android:visibility="gone"
tools:text="(i) Information" />
<androidx.recyclerview.widget.RecyclerView
<de.danoeh.antennapod.view.EpisodeItemListRecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -28,11 +28,6 @@
android:paddingTop="@dimen/list_vertical_padding"
android:paddingBottom="@dimen/list_vertical_padding"
android:layout_above="@id/loadingMore"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/scrollbar_thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/scrollbar_line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/scrollbar_thumb_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/scrollbar_line_drawable"
tools:itemCount="13"
tools:listitem="@layout/feeditemlist_item" />

View File

@ -46,7 +46,7 @@
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
<de.danoeh.antennapod.view.EpisodeItemListRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -32,12 +32,11 @@
android:layout_below="@id/info_bar"
android:background="?android:attr/listDivider"/>
<androidx.recyclerview.widget.RecyclerView
<de.danoeh.antennapod.view.EpisodeItemListRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/divider"
android:scrollbars="vertical"/>
android:layout_below="@id/divider" />
<ProgressBar
android:id="@+id/progLoading"

View File

@ -31,9 +31,9 @@
android:paddingRight="12dp"
android:clipToPadding="false"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_below="@id/recyclerViewFeeds"
<de.danoeh.antennapod.view.EpisodeItemListRecyclerView
android:id="@+id/recyclerView"
android:layout_below="@id/recyclerViewFeeds"
android:layout_marginTop="-4dp"
android:paddingTop="12dp"
android:clipToPadding="false"

View File

@ -11,7 +11,7 @@
android:layout_alignParentTop="true"
android:id="@+id/toolbar"/>
<androidx.recyclerview.widget.RecyclerView
<de.danoeh.antennapod.view.EpisodeItemListRecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/toolbar"

View File

@ -24,4 +24,5 @@
<item name="notification_gpodnet_sync_autherror" type="id"/>
<item name="undobar_button" type="id"/>
<item name="undobar_message" type="id"/>
<item name="episode_item_view_holder" type="id"/>
</resources>