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.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
when (layoutType) {
LayoutType.List -> {
app_list.layoutManager = LinearLayoutManager(this)
app_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
}
startActivity(intent)
}
}
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

View File

@ -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 {
/**
* 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)
enum class LayoutType {
List,
Grid,
}
/**
* 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)
/**
@ -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)
if (layoutType == LayoutType.List) {
holder.icon.setOnClickListener(this)
holder.icon.tag = position
}
holder.card?.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>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>

View File

@ -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>

View File

@ -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"