diff options
author | H. Lehmann <ByteHamster@users.noreply.github.com> | 2020-04-02 19:19:40 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-02 19:19:40 +0200 |
commit | 5e344baf4b0689db9addcfcb07ca14ba6f51c1a0 (patch) | |
tree | 4ded95d5ec8564a41a392d2ac9b5f385af9934de /app/src/main/java/de/danoeh/antennapod | |
parent | 31e02d89c2fd905b01c256751c914e71ea538a3c (diff) | |
parent | 59250404c2c4cd3a999f66567d21e8e24d6a2b4d (diff) | |
download | AntennaPod-5e344baf4b0689db9addcfcb07ca14ba6f51c1a0.zip |
Merge pull request #3993 from ByteHamster/recycle-viewholders
Recycle ViewHolders throughout the whole app
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod')
12 files changed, 166 insertions, 181 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index 7983db393..6b1e5e7d6 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -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; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java index 2f39b599f..d391f1c54 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java @@ -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. diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java index 14f537eb0..428a968c6 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java @@ -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 diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index d5f8775f1..a3c07721a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -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()); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java index 2a7c1f2b1..189e026a1 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -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))); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java index c9502ae4c..9bfb4d6dc 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -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); } } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index 4aac34c60..269c33962 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -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() { - Timeline timeline = new Timeline(getActivity(), controller.getMedia()); - return timeline.processShownotes(); + if (controller.getMedia() != null) { + Timeline timeline = new Timeline(getActivity(), controller.getMedia()); + return timeline.processShownotes(); + } else { + return null; + } } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index 4a7b9603d..dabff7269 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -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. diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index e4f08acd9..6a2b1abef 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -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 diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index 389996b07..1ebf382fe 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -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); diff --git a/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java b/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java new file mode 100644 index 000000000..cbc6ca630 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java @@ -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); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java index a6300b50e..506f0f695 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java +++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java @@ -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; |