Android TV: Implement game selector activity in new Android TV UI
This commit is contained in:
parent
3801f89125
commit
7c14996e3e
|
@ -1,7 +1,8 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 21
|
||||
// Leanback support requires >22
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion "22.0.1"
|
||||
|
||||
lintOptions {
|
||||
|
@ -80,6 +81,9 @@ dependencies {
|
|||
compile 'com.android.support:recyclerview-v7:22.2.0'
|
||||
compile 'com.android.support:design:22.2.0'
|
||||
|
||||
// Android TV UI libraries.
|
||||
compile 'com.android.support:leanback-v17:22.2.0'
|
||||
|
||||
// For showing the banner as a circle a-la Material Design Guidelines
|
||||
compile 'de.hdodenhof:circleimageview:1.2.2'
|
||||
|
||||
|
|
|
@ -33,11 +33,22 @@
|
|||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.TvMainActivity"
|
||||
android:theme="@style/DolphinTvGamecube">
|
||||
|
||||
<!-- This intentfilter marks this Activity as the one that gets launched from Home screen. -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.AddDirectoryActivity"
|
||||
android:theme="@style/DolphinGamecube"
|
||||
|
|
|
@ -226,7 +226,7 @@ public final class MainActivity extends AppCompatActivity implements LoaderManag
|
|||
GameProvider.URI_GAME, // URI of table to query
|
||||
null, // Return all columns
|
||||
GameDatabase.KEY_GAME_PLATFORM + " = ?", // Select by platform
|
||||
new String[]{Integer.toString(id)}, // Platform id is Loader id minus 1
|
||||
new String[]{Integer.toString(id)}, // Platform id is Loader id
|
||||
GameDatabase.KEY_GAME_TITLE + " asc" // Sort by game name, ascending order
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
package org.dolphinemu.dolphinemu.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.FragmentManager;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v17.leanback.app.BrowseFragment;
|
||||
import android.support.v17.leanback.database.CursorMapper;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.CursorObjectAdapter;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.OnItemViewClickedListener;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.support.v17.leanback.widget.RowPresenter;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.adapters.GamePresenter;
|
||||
import org.dolphinemu.dolphinemu.model.Game;
|
||||
import org.dolphinemu.dolphinemu.model.GameDatabase;
|
||||
import org.dolphinemu.dolphinemu.model.GameProvider;
|
||||
import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder;
|
||||
|
||||
public final class TvMainActivity extends Activity
|
||||
{
|
||||
protected BrowseFragment mBrowseFragment;
|
||||
|
||||
private ArrayObjectAdapter mRowsAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_tv_main);
|
||||
|
||||
final FragmentManager fragmentManager = getFragmentManager();
|
||||
mBrowseFragment = (BrowseFragment) fragmentManager.findFragmentById(
|
||||
R.id.fragment_game_list);
|
||||
|
||||
// Set display parameters for the BrowseFragment
|
||||
mBrowseFragment.setHeadersState(BrowseFragment.HEADERS_ENABLED);
|
||||
mBrowseFragment.setTitle(getString(R.string.app_name));
|
||||
mBrowseFragment.setBadgeDrawable(getResources().getDrawable(
|
||||
R.drawable.ic_launcher, null));
|
||||
mBrowseFragment.setBrandColor(getResources().getColor(R.color.dolphin_blue_dark));
|
||||
|
||||
buildRowsAdapter();
|
||||
|
||||
mBrowseFragment.setOnItemViewClickedListener(
|
||||
new OnItemViewClickedListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row)
|
||||
{
|
||||
TvGameViewHolder holder = (TvGameViewHolder) itemViewHolder;
|
||||
// Start the emulation activity and send the path of the clicked ISO to it.
|
||||
Intent intent = new Intent(TvMainActivity.this, EmulationActivity.class);
|
||||
|
||||
intent.putExtra("SelectedGame", holder.path);
|
||||
intent.putExtra("SelectedTitle", holder.title);
|
||||
intent.putExtra("ScreenPath", holder.screenshotPath);
|
||||
|
||||
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
|
||||
TvMainActivity.this,
|
||||
holder.imageScreenshot,
|
||||
"image_game_screenshot");
|
||||
|
||||
startActivity(intent, options.toBundle());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void buildRowsAdapter()
|
||||
{
|
||||
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
|
||||
|
||||
// For each row
|
||||
for (int platformIndex = 0; platformIndex <= Game.PLATFORM_WII_WARE; ++platformIndex)
|
||||
{
|
||||
// Create an adapter for this row.
|
||||
CursorObjectAdapter row = new CursorObjectAdapter(new GamePresenter(platformIndex));
|
||||
|
||||
// Add items to the adapter.
|
||||
Cursor games = getContentResolver().query(
|
||||
GameProvider.URI_GAME, // URI of table to query
|
||||
null, // Return all columns
|
||||
GameDatabase.KEY_GAME_PLATFORM + " = ?", // Select by platform
|
||||
new String[]{Integer.toString(platformIndex)}, // Platform id
|
||||
GameDatabase.KEY_GAME_TITLE + " asc" // Sort by game name, ascending order
|
||||
);
|
||||
|
||||
row.swapCursor(games);
|
||||
row.setMapper(new CursorMapper()
|
||||
{
|
||||
@Override
|
||||
protected void bindColumns(Cursor cursor)
|
||||
{
|
||||
// No-op? Not sure what this does.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object bind(Cursor cursor)
|
||||
{
|
||||
return Game.fromCursor(cursor);
|
||||
}
|
||||
});
|
||||
|
||||
String headerName;
|
||||
switch (platformIndex)
|
||||
{
|
||||
case Game.PLATFORM_GC:
|
||||
headerName = "GameCube Games";
|
||||
break;
|
||||
|
||||
case Game.PLATFORM_WII:
|
||||
headerName = "Wii Games";
|
||||
break;
|
||||
|
||||
case Game.PLATFORM_WII_WARE:
|
||||
headerName = "WiiWare";
|
||||
break;
|
||||
|
||||
default:
|
||||
headerName = "Error";
|
||||
break;
|
||||
}
|
||||
|
||||
// Create a header for this row.
|
||||
HeaderItem header = new HeaderItem(platformIndex, headerName);
|
||||
|
||||
// Create the row, passing it the filled adapter and the header, and give it to the master adapter.
|
||||
mRowsAdapter.add(new ListRow(header, row));
|
||||
}
|
||||
|
||||
mBrowseFragment.setAdapter(mRowsAdapter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package org.dolphinemu.dolphinemu.adapters;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.model.Game;
|
||||
import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder;
|
||||
|
||||
/**
|
||||
* The Leanback library / docs call this a Presenter, but it works very
|
||||
* similarly to a RecyclerView.ViewHolder.
|
||||
*/
|
||||
public final class GamePresenter extends Presenter
|
||||
{
|
||||
private int mPlatform;
|
||||
|
||||
public GamePresenter(int platform)
|
||||
{
|
||||
mPlatform = platform;
|
||||
}
|
||||
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent)
|
||||
{
|
||||
// Create a new view.
|
||||
ImageCardView gameCard = new ImageCardView(parent.getContext())
|
||||
{
|
||||
@Override
|
||||
public void setSelected(boolean selected)
|
||||
{
|
||||
setCardBackground(this, selected);
|
||||
super.setSelected(selected);
|
||||
}
|
||||
};
|
||||
|
||||
gameCard.setMainImageAdjustViewBounds(true);
|
||||
gameCard.setMainImageDimensions(480, 320);
|
||||
gameCard.setMainImageScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
|
||||
gameCard.setFocusable(true);
|
||||
gameCard.setFocusableInTouchMode(true);
|
||||
|
||||
setCardBackground(gameCard, false);
|
||||
|
||||
// Use that view to create a ViewHolder.
|
||||
return new TvGameViewHolder(gameCard);
|
||||
}
|
||||
|
||||
public void onBindViewHolder(ViewHolder viewHolder, Object item)
|
||||
{
|
||||
TvGameViewHolder holder = (TvGameViewHolder) viewHolder;
|
||||
Game game = (Game) item;
|
||||
|
||||
String screenPath = game.getScreenshotPath();
|
||||
|
||||
// Fill in the view contents.
|
||||
Picasso.with(holder.imageScreenshot.getContext())
|
||||
.load(screenPath)
|
||||
.fit()
|
||||
.centerCrop()
|
||||
.noFade()
|
||||
.noPlaceholder()
|
||||
.config(Bitmap.Config.RGB_565)
|
||||
.error(R.drawable.no_banner)
|
||||
.into(holder.imageScreenshot);
|
||||
|
||||
holder.cardParent.setTitleText(game.getTitle());
|
||||
holder.cardParent.setContentText(game.getCompany());
|
||||
|
||||
// TODO These shouldn't be necessary once the move to a DB-based model is complete.
|
||||
holder.gameId = game.getGameId();
|
||||
holder.path = game.getPath();
|
||||
holder.title = game.getTitle();
|
||||
holder.description = game.getDescription();
|
||||
holder.country = game.getCountry();
|
||||
holder.company = game.getCompany();
|
||||
holder.screenshotPath = game.getScreenshotPath();
|
||||
}
|
||||
|
||||
public void onUnbindViewHolder(ViewHolder viewHolder)
|
||||
{
|
||||
// no op
|
||||
}
|
||||
|
||||
public void setCardBackground(ImageCardView view, boolean selected)
|
||||
{
|
||||
int backgroundColor;
|
||||
|
||||
if (selected)
|
||||
{
|
||||
switch (mPlatform)
|
||||
{
|
||||
case Game.PLATFORM_GC:
|
||||
backgroundColor = R.color.dolphin_accent_gamecube;
|
||||
break;
|
||||
|
||||
case Game.PLATFORM_WII:
|
||||
backgroundColor = R.color.dolphin_accent_wii;
|
||||
break;
|
||||
|
||||
case Game.PLATFORM_WII_WARE:
|
||||
backgroundColor = R.color.dolphin_accent_wiiware;
|
||||
break;
|
||||
|
||||
default:
|
||||
backgroundColor = android.R.color.holo_red_dark;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
backgroundColor = R.color.tv_card_unselected;
|
||||
}
|
||||
|
||||
view.setInfoAreaBackgroundColor(view.getResources().getColor(backgroundColor));
|
||||
}
|
||||
}
|
|
@ -3,8 +3,6 @@ package org.dolphinemu.dolphinemu.model;
|
|||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public final class Game
|
||||
{
|
||||
public static final int PLATFORM_GC = 0;
|
||||
|
@ -33,13 +31,13 @@ public final class Game
|
|||
private String mDescription;
|
||||
private String mPath;
|
||||
private String mGameId;
|
||||
private String mScreenshotFolderPath;
|
||||
private String mScreenshotPath;
|
||||
private String mCompany;
|
||||
|
||||
private int mPlatform;
|
||||
private int mCountry;
|
||||
|
||||
public Game(int platform, String title, String description, int country, String path, String gameId, String company)
|
||||
public Game(int platform, String title, String description, int country, String path, String gameId, String company, String screenshotPath)
|
||||
{
|
||||
mPlatform = platform;
|
||||
mTitle = title;
|
||||
|
@ -48,7 +46,7 @@ public final class Game
|
|||
mPath = path;
|
||||
mGameId = gameId;
|
||||
mCompany = company;
|
||||
mScreenshotFolderPath = PATH_SCREENSHOT_FOLDER + getGameId() + "/";
|
||||
mScreenshotPath = screenshotPath;
|
||||
}
|
||||
|
||||
public int getPlatform()
|
||||
|
@ -86,27 +84,9 @@ public final class Game
|
|||
return mGameId;
|
||||
}
|
||||
|
||||
public String getScreenshotFolderPath()
|
||||
public String getScreenshotPath()
|
||||
{
|
||||
return mScreenshotFolderPath;
|
||||
}
|
||||
|
||||
public String getScreenPath()
|
||||
{
|
||||
// Count how many screenshots are available, so we can use the most recent one.
|
||||
File screenshotFolder = new File(mScreenshotFolderPath.substring(mScreenshotFolderPath.indexOf('s') - 1));
|
||||
int screenCount = 0;
|
||||
|
||||
if (screenshotFolder.isDirectory())
|
||||
{
|
||||
screenCount = screenshotFolder.list().length;
|
||||
}
|
||||
|
||||
String screenPath = mScreenshotFolderPath
|
||||
+ getGameId() + "-"
|
||||
+ screenCount + ".png";
|
||||
|
||||
return screenPath;
|
||||
return mScreenshotPath;
|
||||
}
|
||||
|
||||
public static ContentValues asContentValues(int platform, String title, String description, int country, String path, String gameId, String company)
|
||||
|
@ -135,6 +115,7 @@ public final class Game
|
|||
cursor.getInt(GameDatabase.GAME_COLUMN_COUNTRY),
|
||||
cursor.getString(GameDatabase.GAME_COLUMN_PATH),
|
||||
cursor.getString(GameDatabase.GAME_COLUMN_GAME_ID),
|
||||
cursor.getString(GameDatabase.GAME_COLUMN_COMPANY));
|
||||
cursor.getString(GameDatabase.GAME_COLUMN_COMPANY),
|
||||
cursor.getString(GameDatabase.GAME_COLUMN_SCREENSHOT_PATH));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package org.dolphinemu.dolphinemu.viewholders;
|
||||
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* A simple class that stores references to views so that the GameAdapter doesn't need to
|
||||
* keep calling findViewById(), which is expensive.
|
||||
*/
|
||||
public class TvGameViewHolder extends Presenter.ViewHolder
|
||||
{
|
||||
public ImageCardView cardParent;
|
||||
|
||||
public ImageView imageScreenshot;
|
||||
public TextView textGameTitle;
|
||||
public TextView textCompany;
|
||||
|
||||
public String gameId;
|
||||
|
||||
// TODO Not need any of this stuff. Currently only the properties dialog needs it.
|
||||
public String path;
|
||||
public String title;
|
||||
public String description;
|
||||
public int country;
|
||||
public String company;
|
||||
public String screenshotPath;
|
||||
|
||||
public TvGameViewHolder(View itemView)
|
||||
{
|
||||
super(itemView);
|
||||
|
||||
itemView.setTag(this);
|
||||
|
||||
cardParent = (ImageCardView) itemView;
|
||||
imageScreenshot = cardParent.getMainImageView();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<fragment
|
||||
android:name="android.support.v17.leanback.app.BrowseFragment"
|
||||
android:id="@+id/fragment_game_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
|
@ -33,7 +33,6 @@
|
|||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_toStartOf="@+id/button_details"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:maxLines="1"
|
||||
|
|
|
@ -8,4 +8,6 @@
|
|||
<color name="dolphin_accent_gamecube">#651fff</color>
|
||||
|
||||
<color name="circle_grey">#bdbdbd</color>
|
||||
|
||||
<color name="tv_card_unselected">#444444</color>
|
||||
</resources>
|
|
@ -126,6 +126,21 @@
|
|||
<item name="textAllCaps">false</item>
|
||||
</style>
|
||||
|
||||
<!-- Android TV Themes -->
|
||||
<style name="DolphinTvBase" parent="Theme.Leanback">
|
||||
<item name="colorPrimary">@color/dolphin_blue</item>
|
||||
<item name="colorPrimaryDark">@color/dolphin_blue_dark</item>
|
||||
|
||||
<!--enable window content transitions-->
|
||||
<item name="android:windowContentTransitions">true</item>
|
||||
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
||||
</style>
|
||||
|
||||
<style name="DolphinTvGamecube" parent="DolphinTvBase">
|
||||
<item name="colorAccent">@color/dolphin_accent_gamecube</item>
|
||||
</style>
|
||||
|
||||
<style name="InGameMenuOption" parent="Widget.AppCompat.Button.Borderless">
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:fontFamily">sans-serif-condensed</item>
|
||||
|
|
Loading…
Reference in New Issue