Grid Layout Support

With `RecyclerView` being used rather than `ListView` or `GridView`. It's now possible to display the items in a flexible manner and so support for a grid view in addition to the already existing list view was added in.
This commit is contained in:
◱ PixelyIon 2020-04-12 20:47:51 +05:30 committed by ◱ PixelyIon
parent d86d5c1a35
commit c9dcb070ad
7 changed files with 136 additions and 23 deletions

View File

@ -19,11 +19,14 @@ import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import emu.skyline.adapter.AppAdapter import emu.skyline.adapter.AppAdapter
import emu.skyline.adapter.AppItem import emu.skyline.adapter.AppItem
import emu.skyline.adapter.GridLayoutSpan
import emu.skyline.adapter.LayoutType
import emu.skyline.loader.BaseLoader import emu.skyline.loader.BaseLoader
import emu.skyline.loader.NroLoader import emu.skyline.loader.NroLoader
import emu.skyline.utility.GameDialog import emu.skyline.utility.GameDialog
@ -32,6 +35,7 @@ import kotlinx.android.synthetic.main.main_activity.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.math.ceil
class MainActivity : AppCompatActivity(), View.OnClickListener { class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var sharedPreferences: SharedPreferences private lateinit var sharedPreferences: SharedPreferences
@ -131,23 +135,31 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
open_fab.setOnClickListener(this) open_fab.setOnClickListener(this)
log_fab.setOnClickListener(this) log_fab.setOnClickListener(this)
adapter = AppAdapter(this)
val layoutType = LayoutType.values()[sharedPreferences.getString("layout_type", "1")!!.toInt()]
adapter = AppAdapter(this, layoutType)
app_list.adapter = adapter app_list.adapter = adapter
app_list.layoutManager = LinearLayoutManager(this) when (layoutType) {
app_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL)) LayoutType.List -> {
app_list.layoutManager = LinearLayoutManager(this)
startActivity(intent) app_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
} }
}
game_list.onItemLongClickListener = AdapterView.OnItemLongClickListener { parent, _, position, _ -> LayoutType.Grid -> {
val item = parent.getItemAtPosition(position) val itemWidth = 225
if (item is AppItem) { val metrics = resources.displayMetrics
val dialog = GameDialog(item) val span = ceil((metrics.widthPixels / metrics.density) / itemWidth).toInt()
dialog.show(supportFragmentManager, "game")
val layoutManager = GridLayoutManager(this, span)
layoutManager.spanSizeLookup = GridLayoutSpan(adapter, span)
app_list.layoutManager = layoutManager
} }
true true
} }
if (sharedPreferences.getString("search_location", "") == "") { if (sharedPreferences.getString("search_location", "") == "") {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION intent.flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION

View File

@ -67,18 +67,19 @@ class AppItem(val meta: AppEntry) : BaseItem() {
} }
} }
/**
* This enumerates the type of layouts the menu can be in
*/
enum class LayoutType {
List,
Grid,
}
/** /**
* This adapter is used to display all found applications using their metadata * This adapter is used to display all found applications using their metadata
*/ */
internal class AppAdapter(val context: Context?) : HeaderAdapter<AppItem, BaseHeader, RecyclerView.ViewHolder>(), View.OnClickListener { internal class AppAdapter(val context: Context?, private val layoutType: LayoutType) : HeaderAdapter<AppItem, BaseHeader, RecyclerView.ViewHolder>(), View.OnClickListener {
/**
* The icon to use on items that don't have a valid icon
*/
private val missingIcon = context?.resources?.getDrawable(R.drawable.default_icon, context.theme)?.toBitmap(256, 256) private val missingIcon = context?.resources?.getDrawable(R.drawable.default_icon, context.theme)?.toBitmap(256, 256)
/**
* The string to use as a description for items that don't have a valid description
*/
private val missingString = context?.getString(R.string.metadata_missing) private val missingString = context?.getString(R.string.metadata_missing)
/** /**
@ -137,7 +138,7 @@ internal class AppAdapter(val context: Context?) : HeaderAdapter<AppItem, BaseHe
var holder: RecyclerView.ViewHolder? = null var holder: RecyclerView.ViewHolder? = null
if (viewType == Item.ordinal) { if (viewType == Item.ordinal) {
val view = inflater.inflate(R.layout.app_item_linear, parent, false) val view = inflater.inflate(if (layoutType == LayoutType.List) R.layout.app_item_linear else R.layout.app_item_grid, parent, false)
holder = ItemViewHolder(view, view.findViewById(R.id.icon), view.findViewById(R.id.text_title), view.findViewById(R.id.text_subtitle)) holder = ItemViewHolder(view, view.findViewById(R.id.icon), view.findViewById(R.id.text_title), view.findViewById(R.id.text_subtitle))
if (layoutType == LayoutType.List) { if (layoutType == LayoutType.List) {
@ -146,6 +147,14 @@ internal class AppAdapter(val context: Context?) : HeaderAdapter<AppItem, BaseHe
if (context is View.OnLongClickListener) if (context is View.OnLongClickListener)
view.setOnLongClickListener(context as View.OnLongClickListener) view.setOnLongClickListener(context as View.OnLongClickListener)
} else {
holder.card = view.findViewById(R.id.app_item_grid)
if (context is View.OnClickListener)
holder.card!!.setOnClickListener(context as View.OnClickListener)
if (context is View.OnLongClickListener)
holder.card!!.setOnLongClickListener(context as View.OnLongClickListener)
} }
} else if (viewType == Header.ordinal) { } else if (viewType == Header.ordinal) {
val view = inflater.inflate(R.layout.section_item, parent, false) val view = inflater.inflate(R.layout.section_item, parent, false)
@ -171,8 +180,10 @@ internal class AppAdapter(val context: Context?) : HeaderAdapter<AppItem, BaseHe
holder.icon.setImageBitmap(item.icon ?: missingIcon) holder.icon.setImageBitmap(item.icon ?: missingIcon)
holder.icon.setOnClickListener(this) if (layoutType == LayoutType.List) {
holder.icon.tag = position holder.icon.setOnClickListener(this)
holder.icon.tag = position
}
holder.card?.tag = item holder.card?.tag = item
holder.parent.tag = item holder.parent.tag = item

View File

@ -237,3 +237,22 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
} }
} }
} }
/**
* This class is used to lookup the span based on the type of the element
*
* @param adapter The adapter which is used to deduce the type of the item based on the position
* @param headerSpan The span size to return for headers
*/
class GridLayoutSpan<ItemType : BaseItem?, HeaderType : BaseHeader?, ViewHolder : RecyclerView.ViewHolder?>(val adapter: HeaderAdapter<ItemType, HeaderType, ViewHolder>, var headerSpan: Int) : GridLayoutManager.SpanSizeLookup() {
/**
* This returns the size of the span based on the type of the element at [position]
*/
override fun getSpanSize(position: Int): Int {
val item = adapter.getItem(position)!!
return if (item.elementType == ElementType.Item)
1
else
headerSpan
}
}

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/app_item_grid"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_margin="15dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
card_view:cardCornerRadius="4dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/icon"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_alignParentTop="false"
android:layout_centerHorizontal="true"
android:contentDescription="@string/icon" />
<TextView
android:id="@+id/text_title"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_below="@id/icon"
android:paddingStart="15dp"
android:paddingTop="15dp"
android:paddingEnd="15dp"
android:textAlignment="center"
android:textAppearance="?android:attr/textAppearanceListItem"
tools:ignore="RelativeOverlap" />
<TextView
android:id="@+id/text_subtitle"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_below="@id/text_title"
android:layout_alignStart="@id/text_title"
android:paddingBottom="15dp"
android:textAlignment="center"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="@android:color/tertiary_text_light" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@ -12,6 +12,14 @@
<item>2</item> <item>2</item>
<item>3</item> <item>3</item>
</string-array> </string-array>
<string-array name="layout_type">
<item>List</item>
<item>Grid</item>
</string-array>
<string-array name="layout_type_val">
<item>0</item>
<item>1</item>
</string-array>
<string-array name="app_theme"> <string-array name="app_theme">
<item>Light</item> <item>Light</item>
<item>Dark</item> <item>Dark</item>

View File

@ -33,7 +33,7 @@
<string name="handheld_enabled">The system will emulate being in handheld mode</string> <string name="handheld_enabled">The system will emulate being in handheld mode</string>
<string name="docked_enabled">The system will emulate being in docked mode</string> <string name="docked_enabled">The system will emulate being in docked mode</string>
<string name="theme">Theme</string> <string name="theme">Theme</string>
<string name="searching_roms">Searching for ROMs</string> <string name="layout_type">Application Layout</string>
<string name="audio">Audio</string> <string name="audio">Audio</string>
<string name="audren_buffer_size">Audio Buffer Size</string> <string name="audren_buffer_size">Audio Buffer Size</string>
<string name="audren_buffer_desc">The size of the buffer used to store audio samples for ROMs using audren. A lower value will result in less latency but potentially increased audio stutter depending on the performance of the device</string> <string name="audren_buffer_desc">The size of the buffer used to store audio samples for ROMs using audren. A lower value will result in less latency but potentially increased audio stutter depending on the performance of the device</string>

View File

@ -30,6 +30,13 @@
app:key="app_theme" app:key="app_theme"
app:title="@string/theme" app:title="@string/theme"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="1"
android:entries="@array/layout_type"
android:entryValues="@array/layout_type_val"
app:key="layout_type"
app:title="@string/layout_type"
app:useSimpleSummaryProvider="true" />
<ListPreference <ListPreference
android:defaultValue="2" android:defaultValue="2"
android:entries="@array/log_level" android:entries="@array/log_level"