Android: Move game list over to RecyclerView

This commit is contained in:
Connor McLaughlin 2021-03-06 14:42:40 +10:00
parent 5bd39bc2c7
commit 7b45df0cc2
6 changed files with 204 additions and 165 deletions

View File

@ -17,6 +17,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.widget.PopupMenu;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

View File

@ -19,12 +19,10 @@ public class GameList {
private Activity mContext;
private GameListEntry[] mEntries;
private ListViewAdapter mAdapter;
private ArrayList<OnRefreshListener> mRefreshListeners = new ArrayList<>();
public GameList(Activity context) {
mContext = context;
mAdapter = new ListViewAdapter();
mEntries = new GameListEntry[0];
}
@ -58,7 +56,6 @@ public class GameList {
e.printStackTrace();
}
mEntries = newEntries;
mAdapter.notifyDataSetChanged();
for (OnRefreshListener listener : mRefreshListeners)
listener.onGameListRefresh();
});
@ -72,41 +69,4 @@ public class GameList {
public GameListEntry getEntry(int index) {
return mEntries[index];
}
private class ListViewAdapter extends BaseAdapter {
@Override
public int getCount() {
return mEntries.length;
}
@Override
public Object getItem(int position) {
return mEntries[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(mContext)
.inflate(R.layout.game_list_view_entry, parent, false);
}
mEntries[position].fillView(convertView);
return convertView;
}
}
public BaseAdapter getListViewAdapter() {
return mAdapter;
}
}

View File

@ -80,6 +80,8 @@ public class GameListEntry {
return mFileTitle;
}
public long getSize() { return mSize; }
public String getModifiedTime() {
return mModifiedTime;
}
@ -105,88 +107,4 @@ public class GameListEntry {
else
return path;
}
private String getSubTitle() {
String fileName = getFileNameForPath(mPath);
String sizeString = String.format("%.2f MB", (double) mSize / 1048576.0);
return String.format("%s (%s)", fileName, sizeString);
}
public void fillView(View view) {
((TextView) view.findViewById(R.id.game_list_view_entry_title)).setText(mTitle);
((TextView) view.findViewById(R.id.game_list_view_entry_subtitle)).setText(getSubTitle());
int regionDrawableId;
switch (mRegion) {
case NTSC_J:
regionDrawableId = R.drawable.flag_jp;
break;
case PAL:
regionDrawableId = R.drawable.flag_eu;
break;
case Other:
regionDrawableId = R.drawable.ic_baseline_help_24;
break;
case NTSC_U:
default:
regionDrawableId = R.drawable.flag_us;
break;
}
((ImageView) view.findViewById(R.id.game_list_view_entry_region_icon))
.setImageDrawable(ContextCompat.getDrawable(view.getContext(), regionDrawableId));
int typeDrawableId;
switch (mType) {
case PSExe:
typeDrawableId = R.drawable.ic_emblem_system;
break;
case Playlist:
typeDrawableId = R.drawable.ic_baseline_playlist_play_24;
break;
case PSF:
typeDrawableId = R.drawable.ic_baseline_library_music_24;
break;
case Disc:
default:
typeDrawableId = R.drawable.ic_media_cdrom;
break;
}
ImageView icon = ((ImageView) view.findViewById(R.id.game_list_view_entry_type_icon));
icon.setImageDrawable(ContextCompat.getDrawable(view.getContext(), typeDrawableId));
if (mCoverPath != null) {
new ImageLoadTask(icon).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mCoverPath);
}
int compatibilityDrawableId;
switch (mCompatibilityRating) {
case DoesntBoot:
compatibilityDrawableId = R.drawable.ic_star_1;
break;
case CrashesInIntro:
compatibilityDrawableId = R.drawable.ic_star_2;
break;
case CrashesInGame:
compatibilityDrawableId = R.drawable.ic_star_3;
break;
case GraphicalAudioIssues:
compatibilityDrawableId = R.drawable.ic_star_4;
break;
case NoIssues:
compatibilityDrawableId = R.drawable.ic_star_5;
break;
case Unknown:
default:
compatibilityDrawableId = R.drawable.ic_star_0;
break;
}
((ImageView) view.findViewById(R.id.game_list_view_compatibility_icon))
.setImageDrawable(ContextCompat.getDrawable(view.getContext(), compatibilityDrawableId));
}
}

View File

@ -1,20 +1,29 @@
package com.github.stenzek.duckstation;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class GameListFragment extends Fragment {
public class GameListFragment extends Fragment implements GameList.OnRefreshListener {
private MainActivity mParent;
private ListView mGameListView;
private RecyclerView mRecyclerView;
private GameListFragment.ViewAdapter mAdapter;
public GameListFragment(MainActivity parent) {
super(R.layout.fragment_game_list);
@ -29,40 +38,190 @@ public class GameListFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mGameListView = view.findViewById(R.id.game_list_view);
mGameListView.setAdapter(getGameList().getListViewAdapter());
mGameListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mParent.startEmulation(getGameList().getEntry(position).getPath(), mParent.shouldResumeStateByDefault());
mAdapter = new GameListFragment.ViewAdapter(mParent, getGameList());
getGameList().addRefreshListener(this);
mRecyclerView = view.findViewById(R.id.game_list_view);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(mParent));
mRecyclerView.addItemDecoration(new DividerItemDecoration(mRecyclerView.getContext(),
DividerItemDecoration.VERTICAL));
}
@Override
public void onDestroyView() {
super.onDestroyView();
getGameList().removeRefreshListener(this);
}
@Override
public void onGameListRefresh() {
mAdapter.notifyDataSetChanged();
}
private static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
private MainActivity mParent;
private View mItemView;
private GameListEntry mEntry;
public ViewHolder(@NonNull MainActivity parent, @NonNull View itemView) {
super(itemView);
mParent = parent;
mItemView = itemView;
mItemView.setOnClickListener(this);
mItemView.setOnLongClickListener(this);
}
private String getSubTitle() {
String fileName = GameListEntry.getFileNameForPath(mEntry.getPath());
String sizeString = String.format("%.2f MB", (double) mEntry.getSize() / 1048576.0);
return String.format("%s (%s)", fileName, sizeString);
}
public void bindToEntry(GameListEntry entry) {
mEntry = entry;
((TextView) mItemView.findViewById(R.id.game_list_view_entry_title)).setText(entry.getTitle());
((TextView) mItemView.findViewById(R.id.game_list_view_entry_subtitle)).setText(getSubTitle());
int regionDrawableId;
switch (entry.getRegion()) {
case NTSC_J:
regionDrawableId = R.drawable.flag_jp;
break;
case PAL:
regionDrawableId = R.drawable.flag_eu;
break;
case Other:
regionDrawableId = R.drawable.ic_baseline_help_24;
break;
case NTSC_U:
default:
regionDrawableId = R.drawable.flag_us;
break;
}
});
mGameListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
long id) {
PopupMenu menu = new PopupMenu(getContext(), view, Gravity.RIGHT | Gravity.TOP);
menu.getMenuInflater().inflate(R.menu.menu_game_list_entry, menu.getMenu());
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
int id = item.getItemId();
if (id == R.id.game_list_entry_menu_start_game) {
mParent.startEmulation(getGameList().getEntry(position).getPath(), false);
return true;
} else if (id == R.id.game_list_entry_menu_resume_game) {
mParent.startEmulation(getGameList().getEntry(position).getPath(), true);
return true;
} else if (id == R.id.game_list_entry_menu_properties) {
mParent.openGameProperties(getGameList().getEntry(position).getPath());
return true;
}
return false;
((ImageView) mItemView.findViewById(R.id.game_list_view_entry_region_icon))
.setImageDrawable(ContextCompat.getDrawable(mItemView.getContext(), regionDrawableId));
int typeDrawableId;
switch (entry.getType()) {
case PSExe:
typeDrawableId = R.drawable.ic_emblem_system;
break;
case Playlist:
typeDrawableId = R.drawable.ic_baseline_playlist_play_24;
break;
case PSF:
typeDrawableId = R.drawable.ic_baseline_library_music_24;
break;
case Disc:
default:
typeDrawableId = R.drawable.ic_media_cdrom;
break;
}
ImageView icon = ((ImageView) mItemView.findViewById(R.id.game_list_view_entry_type_icon));
icon.setImageDrawable(ContextCompat.getDrawable(mItemView.getContext(), typeDrawableId));
final String coverPath = entry.getCoverPath();
if (coverPath != null) {
new ImageLoadTask(icon).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, coverPath);
}
int compatibilityDrawableId;
switch (entry.getCompatibilityRating()) {
case DoesntBoot:
compatibilityDrawableId = R.drawable.ic_star_1;
break;
case CrashesInIntro:
compatibilityDrawableId = R.drawable.ic_star_2;
break;
case CrashesInGame:
compatibilityDrawableId = R.drawable.ic_star_3;
break;
case GraphicalAudioIssues:
compatibilityDrawableId = R.drawable.ic_star_4;
break;
case NoIssues:
compatibilityDrawableId = R.drawable.ic_star_5;
break;
case Unknown:
default:
compatibilityDrawableId = R.drawable.ic_star_0;
break;
}
((ImageView) mItemView.findViewById(R.id.game_list_view_compatibility_icon))
.setImageDrawable(ContextCompat.getDrawable(mItemView.getContext(), compatibilityDrawableId));
}
@Override
public void onClick(View v) {
mParent.startEmulation(mEntry.getPath(), mParent.shouldResumeStateByDefault());
}
@Override
public boolean onLongClick(View v) {
androidx.appcompat.widget.PopupMenu menu = new androidx.appcompat.widget.PopupMenu(mParent, v, Gravity.RIGHT | Gravity.TOP);
menu.getMenuInflater().inflate(R.menu.menu_game_list_entry, menu.getMenu());
menu.setOnMenuItemClickListener(new androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
int id = item.getItemId();
if (id == R.id.game_list_entry_menu_start_game) {
mParent.startEmulation(mEntry.getPath(), false);
return true;
} else if (id == R.id.game_list_entry_menu_resume_game) {
mParent.startEmulation(mEntry.getPath(), true);
return true;
} else if (id == R.id.game_list_entry_menu_properties) {
mParent.openGameProperties(mEntry.getPath());
return true;
}
});
menu.show();
return true;
}
});
return false;
}
});
menu.show();
return true;
}
}
private static class ViewAdapter extends RecyclerView.Adapter<GameListFragment.ViewHolder> {
private MainActivity mParent;
private LayoutInflater mInflater;
private GameList mGameList;
public ViewAdapter(@NonNull MainActivity parent, @NonNull GameList gameList) {
mParent = parent;
mInflater = LayoutInflater.from(parent);
mGameList = gameList;
}
@NonNull
@Override
public GameListFragment.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new GameListFragment.ViewHolder(mParent, mInflater.inflate(R.layout.layout_game_list_entry, parent, false));
}
@Override
public void onBindViewHolder(@NonNull GameListFragment.ViewHolder holder, int position) {
GameListEntry entry = mGameList.getEntry(position);
holder.bindToEntry(entry);
}
@Override
public int getItemCount() {
return mGameList.getEntryCount();
}
@Override
public int getItemViewType(int position) {
return R.layout.layout_game_list_entry;
}
}
}

View File

@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/game_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -4,14 +4,17 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/game_list_view_entry_type_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:foregroundGravity="center_vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_media_cdrom" />
@ -21,7 +24,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="8dp"
android:layout_marginRight="80dp"
android:focusable="false"
android:focusableInTouchMode="false"
@ -50,7 +52,6 @@
android:id="@+id/game_list_view_compatibility_icon"
android:layout_width="64dp"
android:layout_height="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:focusable="false"
android:focusableInTouchMode="false"