mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-23 20:11:49 +01:00
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:
parent
d86d5c1a35
commit
c9dcb070ad
@ -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
|
||||||
|
|
||||||
|
when (layoutType) {
|
||||||
|
LayoutType.List -> {
|
||||||
app_list.layoutManager = LinearLayoutManager(this)
|
app_list.layoutManager = LinearLayoutManager(this)
|
||||||
app_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
|
app_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
|
||||||
|
}
|
||||||
|
|
||||||
startActivity(intent)
|
LayoutType.Grid -> {
|
||||||
}
|
val itemWidth = 225
|
||||||
}
|
val metrics = resources.displayMetrics
|
||||||
game_list.onItemLongClickListener = AdapterView.OnItemLongClickListener { parent, _, position, _ ->
|
val span = ceil((metrics.widthPixels / metrics.density) / itemWidth).toInt()
|
||||||
val item = parent.getItemAtPosition(position)
|
|
||||||
if (item is AppItem) {
|
val layoutManager = GridLayoutManager(this, span)
|
||||||
val dialog = GameDialog(item)
|
layoutManager.spanSizeLookup = GridLayoutSpan(adapter, span)
|
||||||
dialog.show(supportFragmentManager, "game")
|
|
||||||
|
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
|
||||||
|
@ -68,17 +68,18 @@ class AppItem(val meta: AppEntry) : BaseItem() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This adapter is used to display all found applications using their metadata
|
* This enumerates the type of layouts the menu can be in
|
||||||
*/
|
*/
|
||||||
internal class AppAdapter(val context: Context?) : HeaderAdapter<AppItem, BaseHeader, RecyclerView.ViewHolder>(), View.OnClickListener {
|
enum class LayoutType {
|
||||||
/**
|
List,
|
||||||
* The icon to use on items that don't have a valid icon
|
Grid,
|
||||||
*/
|
}
|
||||||
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
|
* This adapter is used to display all found applications using their metadata
|
||||||
*/
|
*/
|
||||||
|
internal class AppAdapter(val context: Context?, private val layoutType: LayoutType) : HeaderAdapter<AppItem, BaseHeader, RecyclerView.ViewHolder>(), View.OnClickListener {
|
||||||
|
private val missingIcon = context?.resources?.getDrawable(R.drawable.default_icon, context.theme)?.toBitmap(256, 256)
|
||||||
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)
|
||||||
|
|
||||||
|
if (layoutType == LayoutType.List) {
|
||||||
holder.icon.setOnClickListener(this)
|
holder.icon.setOnClickListener(this)
|
||||||
holder.icon.tag = position
|
holder.icon.tag = position
|
||||||
|
}
|
||||||
|
|
||||||
holder.card?.tag = item
|
holder.card?.tag = item
|
||||||
holder.parent.tag = item
|
holder.parent.tag = item
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
56
app/src/main/res/layout/app_item_grid.xml
Normal file
56
app/src/main/res/layout/app_item_grid.xml
Normal 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>
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user