mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-04-23 00:36:41 +00:00
Add new app view UI
This commit is contained in:
@@ -102,11 +102,15 @@
|
|||||||
<orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
|
<orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" exported="" name="bcprov-jdk15on-1.51" level="project" />
|
<orderEntry type="library" exported="" name="bcprov-jdk15on-1.51" level="project" />
|
||||||
|
<orderEntry type="library" exported="" name="androidasync-1.3.7" level="project" />
|
||||||
<orderEntry type="library" exported="" name="jmdns-fixed" level="project" />
|
<orderEntry type="library" exported="" name="jmdns-fixed" level="project" />
|
||||||
<orderEntry type="library" exported="" name="jcodec-0.1.6-3" level="project" />
|
<orderEntry type="library" exported="" name="jcodec-0.1.6-3" level="project" />
|
||||||
<orderEntry type="library" exported="" name="bcpkix-jdk15on-1.51" level="project" />
|
<orderEntry type="library" exported="" name="bcpkix-jdk15on-1.51" level="project" />
|
||||||
<orderEntry type="library" exported="" name="tinyrtsp" level="project" />
|
<orderEntry type="library" exported="" name="tinyrtsp" level="project" />
|
||||||
<orderEntry type="library" exported="" name="limelight-common" level="project" />
|
<orderEntry type="library" exported="" name="limelight-common" level="project" />
|
||||||
|
<orderEntry type="library" exported="" name="support-v4-r6" level="project" />
|
||||||
|
<orderEntry type="library" exported="" name="gson-2.3" level="project" />
|
||||||
|
<orderEntry type="library" exported="" name="ion-1.3.7" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ dependencies {
|
|||||||
compile group: 'org.jcodec', name: 'jcodec', version: '0.1.6-3'
|
compile group: 'org.jcodec', name: 'jcodec', version: '0.1.6-3'
|
||||||
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.51'
|
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.51'
|
||||||
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.51'
|
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.51'
|
||||||
|
compile group: 'com.google.android', name: 'support-v4', version:'r6'
|
||||||
|
compile group: 'com.koushikdutta.ion', name: 'ion', version:'1.3.7'
|
||||||
compile files('libs/jmdns-fixed.jar')
|
compile files('libs/jmdns-fixed.jar')
|
||||||
compile files('libs/limelight-common.jar')
|
compile files('libs/limelight-common.jar')
|
||||||
compile files('libs/tinyrtsp.jar')
|
compile files('libs/tinyrtsp.jar')
|
||||||
|
|||||||
@@ -4,15 +4,17 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
import com.limelight.binding.PlatformBinding;
|
import com.limelight.binding.PlatformBinding;
|
||||||
|
import com.limelight.grid.AppGridAdapter;
|
||||||
import com.limelight.nvstream.http.GfeHttpResponseException;
|
import com.limelight.nvstream.http.GfeHttpResponseException;
|
||||||
import com.limelight.nvstream.http.NvApp;
|
import com.limelight.nvstream.http.NvApp;
|
||||||
import com.limelight.nvstream.http.NvHTTP;
|
import com.limelight.nvstream.http.NvHTTP;
|
||||||
import com.limelight.R;
|
|
||||||
import com.limelight.utils.Dialog;
|
import com.limelight.utils.Dialog;
|
||||||
import com.limelight.utils.SpinnerDialog;
|
import com.limelight.utils.SpinnerDialog;
|
||||||
import com.limelight.utils.UiHelper;
|
import com.limelight.utils.UiHelper;
|
||||||
@@ -27,15 +29,14 @@ import android.view.View;
|
|||||||
import android.view.ContextMenu.ContextMenuInfo;
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.GridView;
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||||
|
|
||||||
public class AppView extends Activity {
|
public class AppView extends Activity {
|
||||||
private ListView appList;
|
private GridView appGrid;
|
||||||
private ArrayAdapter<AppObject> appListAdapter;
|
private AppGridAdapter appGridAdapter;
|
||||||
private InetAddress ipAddress;
|
private InetAddress ipAddress;
|
||||||
private String uniqueId;
|
private String uniqueId;
|
||||||
private boolean remote;
|
private boolean remote;
|
||||||
@@ -60,10 +61,11 @@ public class AppView extends Activity {
|
|||||||
uniqueId = getIntent().getStringExtra(UNIQUEID_EXTRA);
|
uniqueId = getIntent().getStringExtra(UNIQUEID_EXTRA);
|
||||||
remote = getIntent().getBooleanExtra(REMOTE_EXTRA, false);
|
remote = getIntent().getBooleanExtra(REMOTE_EXTRA, false);
|
||||||
if (address == null || uniqueId == null) {
|
if (address == null || uniqueId == null) {
|
||||||
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String labelText = "App List for "+getIntent().getStringExtra(NAME_EXTRA);
|
String labelText = "Apps on "+getIntent().getStringExtra(NAME_EXTRA);
|
||||||
TextView label = (TextView) findViewById(R.id.appListText);
|
TextView label = (TextView) findViewById(R.id.appListText);
|
||||||
setTitle(labelText);
|
setTitle(labelText);
|
||||||
label.setText(labelText);
|
label.setText(labelText);
|
||||||
@@ -71,20 +73,26 @@ public class AppView extends Activity {
|
|||||||
try {
|
try {
|
||||||
ipAddress = InetAddress.getByAddress(address);
|
ipAddress = InetAddress.getByAddress(address);
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
return;
|
e.printStackTrace();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the list view
|
// Setup the list view
|
||||||
appList = (ListView)findViewById(R.id.pcListView);
|
appGrid = (GridView)findViewById(R.id.appGridView);
|
||||||
appListAdapter = new ArrayAdapter<AppObject>(this, R.layout.simplerow, R.id.rowTextView);
|
try {
|
||||||
appListAdapter.setNotifyOnChange(false);
|
appGridAdapter = new AppGridAdapter(this, ipAddress, uniqueId);
|
||||||
appList.setAdapter(appListAdapter);
|
} catch (Exception e) {
|
||||||
appList.setItemsCanFocus(true);
|
e.printStackTrace();
|
||||||
appList.setOnItemClickListener(new OnItemClickListener() {
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appGrid.setAdapter(appGridAdapter);
|
||||||
|
appGrid.setOnItemClickListener(new OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
||||||
long id) {
|
long id) {
|
||||||
AppObject app = appListAdapter.getItem(pos);
|
AppObject app = (AppObject) appGridAdapter.getItem(pos);
|
||||||
if (app == null || app.app == null) {
|
if (app == null || app.app == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -98,7 +106,7 @@ public class AppView extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
registerForContextMenu(appList);
|
registerForContextMenu(appGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -118,8 +126,8 @@ public class AppView extends Activity {
|
|||||||
|
|
||||||
private int getRunningAppId() {
|
private int getRunningAppId() {
|
||||||
int runningAppId = -1;
|
int runningAppId = -1;
|
||||||
for (int i = 0; i < appListAdapter.getCount(); i++) {
|
for (int i = 0; i < appGridAdapter.getCount(); i++) {
|
||||||
AppObject app = appListAdapter.getItem(i);
|
AppObject app = (AppObject) appGridAdapter.getItem(i);
|
||||||
if (app.app == null) {
|
if (app.app == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -137,7 +145,7 @@ public class AppView extends Activity {
|
|||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||||
AppObject selectedApp = appListAdapter.getItem(info.position);
|
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
|
||||||
if (selectedApp == null || selectedApp.app == null) {
|
if (selectedApp == null || selectedApp.app == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -162,7 +170,7 @@ public class AppView extends Activity {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
public boolean onContextItemSelected(MenuItem item) {
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
||||||
AppObject app = appListAdapter.getItem(info.position);
|
AppObject app = (AppObject) appGridAdapter.getItem(info.position);
|
||||||
switch (item.getItemId())
|
switch (item.getItemId())
|
||||||
{
|
{
|
||||||
case RESUME_ID:
|
case RESUME_ID:
|
||||||
@@ -191,12 +199,8 @@ public class AppView extends Activity {
|
|||||||
return str.toString();
|
return str.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addListPlaceholder() {
|
|
||||||
appListAdapter.add(new AppObject("No apps found. Try rescanning for games in GeForce Experience.", null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateAppList() {
|
private void updateAppList() {
|
||||||
final SpinnerDialog spinner = SpinnerDialog.displayDialog(this, "App List", "Refreshing app list...", true);
|
final SpinnerDialog spinner = SpinnerDialog.displayDialog(this, "App List", "Refreshing apps...", true);
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -208,17 +212,12 @@ public class AppView extends Activity {
|
|||||||
AppView.this.runOnUiThread(new Runnable() {
|
AppView.this.runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
appListAdapter.clear();
|
appGridAdapter.clear();
|
||||||
if (appList.isEmpty()) {
|
for (NvApp app : appList) {
|
||||||
addListPlaceholder();
|
appGridAdapter.addApp(new AppObject(generateString(app), app));
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
for (NvApp app : appList) {
|
appGridAdapter.notifyDataSetChanged();
|
||||||
appListAdapter.add(new AppObject(generateString(app), app));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appListAdapter.notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -282,17 +281,15 @@ public class AppView extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class AppObject {
|
public class AppObject {
|
||||||
public String text;
|
|
||||||
public NvApp app;
|
public NvApp app;
|
||||||
|
|
||||||
public AppObject(String text, NvApp app) {
|
public AppObject(String text, NvApp app) {
|
||||||
this.text = text;
|
|
||||||
this.app = app;
|
this.app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return text;
|
return app.getAppName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -676,7 +676,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
||||||
stopConnection();
|
stopConnection();
|
||||||
Dialog.displayDialog(this, "Connection Terminated", "The connection failed unexpectedly", true);
|
Dialog.displayDialog(this, "Connection Terminated", "The connection was terminated", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
155
app/src/main/java/com/limelight/grid/AppGridAdapter.java
Normal file
155
app/src/main/java/com/limelight/grid/AppGridAdapter.java
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package com.limelight.grid;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.koushikdutta.async.future.FutureCallback;
|
||||||
|
import com.koushikdutta.ion.Ion;
|
||||||
|
import com.limelight.AppView;
|
||||||
|
import com.limelight.R;
|
||||||
|
import com.limelight.binding.PlatformBinding;
|
||||||
|
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||||
|
|
||||||
|
private InetAddress address;
|
||||||
|
private String uniqueId;
|
||||||
|
private LimelightCryptoProvider cryptoProvider;
|
||||||
|
private SSLContext sslContext;
|
||||||
|
private HashMap<ImageView, Future> pendingRequests = new HashMap<ImageView, Future>();
|
||||||
|
|
||||||
|
public AppGridAdapter(Context context, InetAddress address, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException {
|
||||||
|
super(context, R.layout.app_grid_item, R.drawable.image_loading);
|
||||||
|
|
||||||
|
this.address = address;
|
||||||
|
this.uniqueId = uniqueId;
|
||||||
|
|
||||||
|
cryptoProvider = PlatformBinding.getCryptoProvider(context);
|
||||||
|
|
||||||
|
sslContext = SSLContext.getInstance("SSL");
|
||||||
|
sslContext.init(ourKeyman, trustAllCerts, new SecureRandom());
|
||||||
|
}
|
||||||
|
|
||||||
|
TrustManager[] trustAllCerts = new TrustManager[] {
|
||||||
|
new X509TrustManager() {
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return new X509Certificate[0];
|
||||||
|
}
|
||||||
|
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||||
|
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
||||||
|
}};
|
||||||
|
|
||||||
|
KeyManager[] ourKeyman = new KeyManager[] {
|
||||||
|
new X509KeyManager() {
|
||||||
|
public String chooseClientAlias(String[] keyTypes,
|
||||||
|
Principal[] issuers, Socket socket) {
|
||||||
|
return "Limelight-RSA";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String chooseServerAlias(String keyType, Principal[] issuers,
|
||||||
|
Socket socket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate[] getCertificateChain(String alias) {
|
||||||
|
return new X509Certificate[] {cryptoProvider.getClientCertificate()};
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateKey getPrivateKey(String alias) {
|
||||||
|
return cryptoProvider.getClientPrivateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ignore differences between given hostname and certificate hostname
|
||||||
|
HostnameVerifier hv = new HostnameVerifier() {
|
||||||
|
public boolean verify(String hostname, SSLSession session) { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
public void addApp(AppView.AppObject app) {
|
||||||
|
itemList.add(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void abortPendingRequests() {
|
||||||
|
HashMap<ImageView, Future> tempMap;
|
||||||
|
|
||||||
|
synchronized (pendingRequests) {
|
||||||
|
// Copy the pending requests under a lock
|
||||||
|
tempMap = new HashMap<ImageView, Future>(pendingRequests);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Future f : tempMap.values()) {
|
||||||
|
f.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (pendingRequests) {
|
||||||
|
// Remove cancelled requests
|
||||||
|
for (ImageView v : tempMap.keySet()) {
|
||||||
|
pendingRequests.remove(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean populateImageView(final ImageView imgView, AppView.AppObject obj) {
|
||||||
|
|
||||||
|
// Set SSL contexts correctly to allow us to authenticate
|
||||||
|
Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setTrustManagers(trustAllCerts);
|
||||||
|
Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setSSLContext(sslContext);
|
||||||
|
|
||||||
|
// Set off the deferred image load
|
||||||
|
synchronized (pendingRequests) {
|
||||||
|
Future f = Ion.with(imgView)
|
||||||
|
.placeholder(defaultImageRes)
|
||||||
|
.error(defaultImageRes)
|
||||||
|
.load("https://" + address.getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" +
|
||||||
|
obj.app.getAppId() + "&AssetType=2&AssetIdx=0")
|
||||||
|
.setCallback(new FutureCallback<ImageView>() {
|
||||||
|
@Override
|
||||||
|
public void onCompleted(Exception e, ImageView result) {
|
||||||
|
synchronized (pendingRequests) {
|
||||||
|
pendingRequests.remove(imgView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pendingRequests.put(imgView, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean populateTextView(TextView txtView, AppView.AppObject obj) {
|
||||||
|
// Return false to use the app's toString method
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,10 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
|||||||
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
itemList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return itemList.size();
|
return itemList.size();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import com.limelight.R;
|
|||||||
public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||||
|
|
||||||
public PcGridAdapter(Context context) {
|
public PcGridAdapter(Context context) {
|
||||||
super(context, R.layout.generic_grid_item, R.drawable.computer);
|
super(context, R.layout.pc_grid_item, R.drawable.computer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addComputer(PcView.ComputerObject computer) {
|
public void addComputer(PcView.ComputerObject computer) {
|
||||||
|
|||||||
BIN
app/src/main/res/drawable/image_loading.png
Normal file
BIN
app/src/main/res/drawable/image_loading.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -8,18 +8,18 @@
|
|||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
tools:context=".AppView" >
|
tools:context=".AppView" >
|
||||||
|
|
||||||
<ListView
|
<GridView
|
||||||
android:id="@+id/pcListView"
|
android:id="@+id/appGridView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:numColumns="auto_fit"
|
||||||
|
android:columnWidth="160dp"
|
||||||
|
android:gravity="center"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_below="@+id/appListText"
|
android:layout_below="@+id/appListText">
|
||||||
android:fastScrollEnabled="true"
|
</GridView>
|
||||||
android:longClickable="false"
|
|
||||||
android:stackFromBottom="false">
|
|
||||||
</ListView>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/appListText"
|
android:id="@+id/appListText"
|
||||||
|
|||||||
22
app/src/main/res/layout/app_grid_item.xml
Normal file
22
app/src/main/res/layout/app_grid_item.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="30dp">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/grid_image"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="150dp">
|
||||||
|
</ImageView>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/grid_text"
|
||||||
|
android:layout_width="125dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/grid_image"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="20sp" >
|
||||||
|
</TextView>
|
||||||
|
</RelativeLayout>
|
||||||
Reference in New Issue
Block a user