Merge pull request #3993 from ByteHamster/recycle-viewholders
Recycle ViewHolders throughout the whole app
This commit is contained in:
commit
5e344baf4b
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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" />
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue