这是“ Android完整应用程序教程”系列的第七部分。 完整的应用程序旨在提供一种通过互联网搜索表演电影/演员的简便方法。 在本系列的第一部分( “主要活动UI” )中,我们创建了Eclipse项目并为应用程序的主要活动设置了基本界面。 在第二部分( “使用HTTP API” )中,我们使用Apache HTTP客户端库来使用外部HTTP API并将API的搜索功能集成到我们的应用程序中。 在第三部分( “解析XML响应” )中,我们看到了如何使用Android内置的XML解析功能来解析XML响应。 在第四部分( “从主要活动异步执行API请求” )中,我们将HTTP检索器和XML解析器服务绑定在一起,以便从应用程序的主要活动中执行API搜索请求。 为了避免阻塞主UI线程,该请求是在后台线程中异步执行的。 在第五部分( “使用意图启动新活动” )中,我们了解了如何启动新的活动以及如何将数据从一个活动转移到另一个活动 。 在第六部分( “用于数据表示的自定义列表视图” )中,我们创建了一个自定义列表视图,以提供更好的数据可视化表示。 在这一部分中,我们将创建选项菜单和自定义对话框,以促进更好的用户交互。
在为资源(CPU,内存或屏幕大小)有限的移动设备开发应用程序时,我们应努力最大程度地利用可用的屏幕空间。 在Android中,充分利用屏幕尺寸的一种方法是在必须向用户展示可用选项时使用选项菜单。 正如您在下图中看到的那样,在正常操作下,用户看不到选项,只有在用户提示它们后,这些选项才会出现。 发生这种情况时,菜单将放置在最前面,这意味着它“隐藏”了其下方存在的任何其他UI元素。
创建和使用Android菜单的一个特殊方面是,它们为用户提供了一个熟悉的界面,供用户访问应用程序功能和设置。 换句话说,不要重新发明轮子,只需在想要创建菜单时使用内置功能即可。 Android官方文档页面包含有关创建菜单的全面指南。 在本教程中,我们将看到创建菜单并使用适当的选项填充菜单的最快,最直接的方法。
毫无疑问,构建选项菜单的主要类别是Menu 。 它实际上是用于管理菜单中项目的界面。 默认情况下,每个“ 活动”都支持操作或选项的选项菜单。 更具体地说,Android Activity类提供了一个名为onCreateOptionsMenu的方法,该方法可用于初始化活动的标准选项菜单的内容。 当覆盖该方法时,我们通常使用add方法以便将新项目添加到菜单中。 注意,add方法及其 变体返回一个MenuItem对象,该对象是直接访问以前创建的菜单项的接口。 MenuItem可以同时包含标题和图标。
add方法的itemId参数是一个使我们能够确定选择了哪些选项的参数。 通常,要处理诸如用户选择菜单选项之类的事件,我们需要覆盖Activity的onOptionsItemSelected方法。 该方法包含对所选MenuItem的引用,我们可以使用getItemId方法来找到其标识符。 因此,我们现在可以知道用户选择了什么。
在我们的应用程序中,我们将使用选项菜单,以便允许用户访问特定电影的IMDB页面,或在新窗口中查看其海报图像。 我们首先定义可用项目的ID,然后通过将菜单项添加到菜单来创建菜单。 请注意,我们为菜单项同时使用标题和图标。 最后,当选择一个选项时,我们检查菜单项ID,以找出必须执行的操作。 以下是上述所有代码的片段:
private static final int ITEM_VISIT_IMDB = 0;
private static final int ITEM_VIEW_FULL_IMAGE = 1;
...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, ITEM_VISIT_IMDB, 0,
getString(R.string.visit_imdb)).setIcon(android.R.drawable.ic_menu_set_as);
menu.add(Menu.NONE, ITEM_VIEW_FULL_IMAGE, 0,
getString(R.string.view_full_image)).setIcon(android.R.drawable.ic_menu_zoom);
return super.onCreateOptionsMenu(menu);
}
...
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case ITEM_VISIT_IMDB:
visitImdbMoviePage();
return true;
case ITEM_VIEW_FULL_IMAGE:
viewFullImagePoster();
return true;
}
return false;
}
这是我们的选项菜单的外观图:
本教程的第二个主题是创建和使用对话框,这些对话框有助于与应用程序的用户进行交互。 最简单的方法是直接使用Dialog类。 Dialog构造函数需要一个Context对象作为参数,这将是在其中创建对话框的Activity 。 与Activity一样 ,对话框对象提供了setContentView方法,该方法接受布局资源ID并从该布局资源设置屏幕内容。 类似地,findViewById方法允许我们从XML布局中查找由id属性标识的视图的引用。 然后可以像往常一样操纵内部视图。 用两个词来说,就像为Activity创建一个View一样。
创建完对话框后,对show方法的调用将启动该对话框并将其显示在屏幕上。 在这种情况下,窗口将放置在应用程序层中并且是不透明的。 在完成特定对话框之后,我们必须调用dismiss方法才能将其从屏幕上删除。 请注意,可以从任何线程安全地调用此方法。 关于Dialog的最后一点是,您可以为它定义一个标题(通过setTitle方法),或者指示它不需要标题(通过requestWindowFeature方法和FEATURE_NO_TITLE功能)。
在我们的应用程序中,我们将创建两种不同类型的对话框:一种用于显示电影的海报图像,另一种用于呈现电影的概述。 让我们看看这些代码片段。
对于海报图像对话框,我们有:
final Dialog dialog = new Dialog(this);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.full_image_layout);
...
final Button closeDialogButton = (Button) dialog.findViewById(R.id.close_full_image_dialog_button);
imageView = (ImageView) dialog.findViewById(R.id.image_view);
closeDialogButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
final ImageDownloaderTask task = new ImageDownloaderTask();
task.execute(imageUrl);
dialog.show();
请注意,该图像是使用ImageDownloaderTask异步检索的(扩展了AsyncTask类),并且在onPostExecute方法中填充了ImageView 。 对于该对话框,布局XML名为“ full_image_layout.xml”,它如下所示:
按下适当的菜单按钮时,将在海报图像对话框中获得以下内容:
对于电影概述对话框,我们具有以下内容:
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.movie_overview_dialog);
dialog.setTitle(title);
final TextView overviewTextView = (TextView) dialog.findViewById(R.id.movie_overview_text_view);
overviewTextView.setText(overview);
final Button closeButton = (Button) dialog.findViewById(R.id.movie_overview_close_button);
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
XML布局文件分别名为“ movie_overview_dialog.xml”,它是以下内容:
以下是电影概述对话框的示例(请注意,当用户单击列表中的一部电影时,将显示此对话框):
总而言之,“ MoviesListActivity”类现在如下所示:
package com.javacodegeeks.android.apps.moviesearchapp;
import java.io.InputStream;
import java.util.ArrayList;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.javacodegeeks.android.apps.moviesearchapp.io.FlushedInputStream;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
import com.javacodegeeks.android.apps.moviesearchapp.services.HttpRetriever;
import com.javacodegeeks.android.apps.moviesearchapp.ui.MoviesAdapter;
import com.javacodegeeks.android.apps.moviesearchapp.util.Utils;
public class MoviesListActivity extends ListActivity {
private static final String IMDB_BASE_URL = "http://m.imdb.com/title/";
private static final int ITEM_VISIT_IMDB = 0;
private static final int ITEM_VIEW_FULL_IMAGE = 1;
private ArrayList moviesList = new ArrayList();
private MoviesAdapter moviesAdapter;
private HttpRetriever httpRetriever = new HttpRetriever();
private ProgressDialog progressDialog;
private ImageView imageView;
@SuppressWarnings("unchecked")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.movies_layout);
moviesAdapter = new MoviesAdapter(this, R.layout.movie_data_row, moviesList);
moviesList = (ArrayList) getIntent().getSerializableExtra("movies");
setListAdapter(moviesAdapter);
if (moviesList!=null && !moviesList.isEmpty()) {
moviesAdapter.notifyDataSetChanged();
moviesAdapter.clear();
for (int i = 0; i < moviesList.size(); i++) {
moviesAdapter.add(moviesList.get(i));
}
}
moviesAdapter.notifyDataSetChanged();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, ITEM_VISIT_IMDB, 0,
getString(R.string.visit_imdb)).setIcon(android.R.drawable.ic_menu_set_as);
menu.add(Menu.NONE, ITEM_VIEW_FULL_IMAGE, 0,
getString(R.string.view_full_image)).setIcon(android.R.drawable.ic_menu_zoom);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case ITEM_VISIT_IMDB:
visitImdbMoviePage();
return true;
case ITEM_VIEW_FULL_IMAGE:
viewFullImagePoster();
return true;
}
return false;
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
final Movie movie = moviesAdapter.getItem((int)position);
showMovieOverviewDialog(movie.name, movie.overview);
}
private void viewFullImagePoster() {
final Movie movie = retrieveSelectedMovie();
if (movie==null) {
longToast(getString(R.string.no_movie_selected));
return;
}
String imageUrl = movie.retrieveCoverImage();
if (Utils.isMissing(imageUrl)) {
longToast(getString(R.string.no_imdb_id_found));
return;
}
final Dialog dialog = new Dialog(this);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.full_image_layout);
final Button closeDialogButton = (Button) dialog.findViewById(R.id.close_full_image_dialog_button);
imageView = (ImageView) dialog.findViewById(R.id.image_view);
closeDialogButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
final ImageDownloaderTask task = new ImageDownloaderTask();
task.execute(imageUrl);
dialog.show();
progressDialog = ProgressDialog.show(MoviesListActivity.this,
"Please wait...", "Retrieving data...", true, true);
progressDialog.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
if (task!=null) {
task.cancel(true);
}
}
});
}
private void visitImdbMoviePage() {
final Movie movie = retrieveSelectedMovie();
if (movie==null) {
longToast(getString(R.string.no_movie_selected));
return;
}
String imdbId = movie.imdbId;
if (Utils.isMissing(imdbId)) {
longToast(getString(R.string.no_imdb_id_found));
return;
}
String imdbUrl = IMDB_BASE_URL + movie.imdbId;
Intent imdbIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(imdbUrl));
startActivity(imdbIntent);
}
private void showMovieOverviewDialog(final String title, final String overview) {
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.movie_overview_dialog);
dialog.setTitle(title);
final TextView overviewTextView = (TextView) dialog.findViewById(R.id.movie_overview_text_view);
overviewTextView.setText(overview);
final Button closeButton = (Button) dialog.findViewById(R.id.movie_overview_close_button);
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
}
private class ImageDownloaderTask extends AsyncTask {
@Override
protected Bitmap doInBackground(String... params) {
String url = params[0];
InputStream is = httpRetriever.retrieveStream(url);
if (is==null) {
return null;
}
return BitmapFactory.decodeStream(new FlushedInputStream(is));
}
@Override
protected void onPostExecute(final Bitmap result) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (progressDialog!=null) {
progressDialog.dismiss();
progressDialog = null;
}
if (result!=null) {
imageView.setImageBitmap(result);
}
}
});
}
}
private Movie retrieveSelectedMovie() {
int position = getSelectedItemPosition();
if (position==-1) {
return null;
}
return moviesAdapter.getItem((int)position);
}
private void longToast(CharSequence message) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
}
伙计们。 您可以在此处找到到目前为止创建的Eclipse项目。 干杯!
- “ Android完整应用程序教程”系列
- Android文字转语音应用
- 带有Yahoo API的Android反向地理编码– PlaceFinder
- Android基于位置的服务应用程序– GPS位置
- 使用VirtualBox在PC上安装Android OS
- 拥抱Android的强大功能:快速概述
翻译自: https://www.javacodegeeks.com/2010/12/android-full-app-part-7-options-menus.html