mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-23 11:51:47 +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.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import emu.skyline.adapter.AppAdapter
|
||||
import emu.skyline.adapter.AppItem
|
||||
import emu.skyline.adapter.GridLayoutSpan
|
||||
import emu.skyline.adapter.LayoutType
|
||||
import emu.skyline.loader.BaseLoader
|
||||
import emu.skyline.loader.NroLoader
|
||||
import emu.skyline.utility.GameDialog
|
||||
@ -32,6 +35,7 @@ import kotlinx.android.synthetic.main.main_activity.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.math.ceil
|
||||
|
||||
class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
@ -131,23 +135,31 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
setSupportActionBar(toolbar)
|
||||
open_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.layoutManager = LinearLayoutManager(this)
|
||||
app_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
|
||||
|
||||
startActivity(intent)
|
||||
when (layoutType) {
|
||||
LayoutType.List -> {
|
||||
app_list.layoutManager = LinearLayoutManager(this)
|
||||
app_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
|
||||
}
|
||||
}
|
||||
game_list.onItemLongClickListener = AdapterView.OnItemLongClickListener { parent, _, position, _ ->
|
||||
val item = parent.getItemAtPosition(position)
|
||||
if (item is AppItem) {
|
||||
val dialog = GameDialog(item)
|
||||
dialog.show(supportFragmentManager, "game")
|
||||
|
||||
LayoutType.Grid -> {
|
||||
val itemWidth = 225
|
||||
val metrics = resources.displayMetrics
|
||||
val span = ceil((metrics.widthPixels / metrics.density) / itemWidth).toInt()
|
||||
|
||||
val layoutManager = GridLayoutManager(this, span)
|
||||
layoutManager.spanSizeLookup = GridLayoutSpan(adapter, span)
|
||||
|
||||
app_list.layoutManager = layoutManager
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
if (sharedPreferences.getString("search_location", "") == "") {
|
||||
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
|
||||
|
@ -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
|
||||
*/
|
||||
internal class AppAdapter(val context: Context?) : HeaderAdapter<AppItem, BaseHeader, RecyclerView.ViewHolder>(), View.OnClickListener {
|
||||
/**
|
||||
* The icon to use on items that don't have a valid icon
|
||||
*/
|
||||
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)
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
||||
/**
|
||||
@ -137,7 +138,7 @@ internal class AppAdapter(val context: Context?) : HeaderAdapter<AppItem, BaseHe
|
||||
var holder: RecyclerView.ViewHolder? = null
|
||||
|
||||
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))
|
||||
|
||||
if (layoutType == LayoutType.List) {
|
||||
@ -146,6 +147,14 @@ internal class AppAdapter(val context: Context?) : HeaderAdapter<AppItem, BaseHe
|
||||
|
||||
if (context is 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) {
|
||||
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.setOnClickListener(this)
|
||||
holder.icon.tag = position
|
||||
if (layoutType == LayoutType.List) {
|
||||
holder.icon.setOnClickListener(this)
|
||||
holder.icon.tag = position
|
||||
}
|
||||
|
||||
holder.card?.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>3</item>
|
||||
</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">
|
||||
<item>Light</item>
|
||||
<item>Dark</item>
|
||||
|
@ -33,7 +33,7 @@
|
||||
<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="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="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>
|
||||
|
@ -30,6 +30,13 @@
|
||||
app:key="app_theme"
|
||||
app:title="@string/theme"
|
||||
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
|
||||
android:defaultValue="2"
|
||||
android:entries="@array/log_level"
|
||||
|
Loading…
Reference in New Issue
Block a user