在我们的教程中用来显示搜索结果的视图非常简单。 如果您还记得的话,结果将传递到ListActivity ,它在呈现时如下所示:
现在,我们将为活动的UI增添趣味。 第一步是替换到目前为止使用的ArrayAdapter并实现自定义适配器。 我们的适配器将扩展ArrayAdapter类并重写其getView方法,以提供一个自定义列表View 。
请记住,Movie模型类包含有关相应电影的各种信息,其中包括以下内容:
- 电影分级
- 发布日期
- 资质认证
- 语言
- 缩图图片网址
我们将创建一个自定义布局,其中将包含搜索结果中包含的每部电影的上述数据。 这是列表的每一行的图像:
(注意:我们的实现基于此处提供的出色示例)
描述每行布局的XML文件名为“ movie_data_row.xml”,其中包含以下内容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip">
<ImageView
android:id="@+id/movie_thumb_icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginRight="6dip"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="fill_parent">
<TextView
android:id="@+id/name_text_view"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="marquee"
android:textStyle="bold"
/>
<TextView
android:id="@+id/rating_text_view"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="marquee"
/>
<TextView
android:id="@+id/released_text_view"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="marquee"
/>
<TextView
android:id="@+id/certification_text_view"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="marquee"
/>
<TextView
android:id="@+id/language_text_view"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="marquee"
/>
<TextView
android:id="@+id/adult_text_view"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="marquee"
/>
</LinearLayout>
</LinearLayout>
我们将LinearLayout用于基本布局,并在其中包含ImageView (将保留缩略图)和另一个LinearLayout , LinearLayout是许多TextView的占位符。 每个元素都分配有唯一的ID,以便以后可以从我们的适配器中引用它。
请注意, ArrayAdapter不能使用Activity通常使用的setContentView方法来声明将要使用的布局。 在运行时检索XML布局的方法是使用LayoutInflater服务。 此类用于将布局XML文件实例化为其对应的View对象。 更具体地,充气法,以便用于膨胀从指定的XML资源的新视图层次结构。 引用基础视图之后,我们可以照常使用它并修改其内部小部件,即将文本提供给TextView并将图像加载到ImageView中 。 这是我们的适配器的代码:
package com.javacodegeeks.android.apps.moviesearchapp.ui;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.javacodegeeks.android.apps.moviesearchapp.R;
import com.javacodegeeks.android.apps.moviesearchapp.io.FlushedInputStream;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
import com.javacodegeeks.android.apps.moviesearchapp.services.HttpRetriever;
public class MoviesAdapter extends ArrayAdapter<Movie> {
private HttpRetriever httpRetriever = new HttpRetriever();
private ArrayList<Movie> movieDataItems;
private Activity context;
public MoviesAdapter(Activity context, int textViewResourceId, ArrayList<Movie> movieDataItems) {
super(context, textViewResourceId, movieDataItems);
this.context = context;
this.movieDataItems = movieDataItems;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.movie_data_row, null);
}
Movie movie = movieDataItems.get(position);
if (movie != null) {
// name
TextView nameTextView = (TextView) view.findViewById(R.id.name_text_view);
nameTextView.setText(movie.name);
// rating
TextView ratingTextView = (TextView) view.findViewById(R.id.rating_text_view);
ratingTextView.setText("Rating: " + movie.rating);
// released
TextView releasedTextView = (TextView) view.findViewById(R.id.released_text_view);
releasedTextView.setText("Release Date: " + movie.released);
// certification
TextView certificationTextView = (TextView) view.findViewById(R.id.certification_text_view);
certificationTextView.setText("Certification: " + movie.certification);
// language
TextView languageTextView = (TextView) view.findViewById(R.id.language_text_view);
languageTextView.setText("Language: " + movie.language);
// thumb image
ImageView imageView = (ImageView) view.findViewById(R.id.movie_thumb_icon);
String url = movie.retrieveThumbnail();
if (url!=null) {
Bitmap bitmap = fetchBitmapFromCache(url);
if (bitmap==null) {
new BitmapDownloaderTask(imageView).execute(url);
}
else {
imageView.setImageBitmap(bitmap);
}
}
else {
imageView.setImageBitmap(null);
}
}
return view;
}
private LinkedHashMap<String, Bitmap> bitmapCache = new LinkedHashMap<String, Bitmap>();
private void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (bitmapCache) {
bitmapCache.put(url, bitmap);
}
}
}
private Bitmap fetchBitmapFromCache(String url) {
synchronized (bitmapCache) {
final Bitmap bitmap = bitmapCache.get(url);
if (bitmap != null) {
// Bitmap found in cache
// Move element to first position, so that it is removed last
bitmapCache.remove(url);
bitmapCache.put(url, bitmap);
return bitmap;
}
}
return null;
}
private class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
protected Bitmap doInBackground(String... params) {
url = params[0];
InputStream is = httpRetriever.retrieveStream(url);
if (is==null) {
return null;
}
return BitmapFactory.decodeStream(new FlushedInputStream(is));
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
addBitmapToCache(url, bitmap);
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
}
在我们的getView方法中,我们首先将XML布局文件膨胀,并检索所描述View的引用。 然后,我们使用findViewById方法引用每个视图小部件。 对于每个TextView,我们提供相关的文本,而对于每个ImageView,我们提供包含缩略图的位图 。
这时使用了非常基本的缓存机制,以避免一次又一次地重新下载同一映像。 不要忘记,随着用户使用该接口, getView方法将被多次调用,因此我们绝对不希望对同一图像执行HTTP请求。 因此,使用了包含URL-位图关联的映射。 如果在高速缓存中找不到该图像,则启动后台任务以检索该图像(并将其存储在高速缓存中以供下次调用)。 后台任务名为“ BitmapDownloaderTask”,它扩展了AsyncTask类(如果您想了解有关如何使用AsyncTask的更多信息,请检查我们前面的 教程之一)。
还要注意,在每个任务中, ImageView实例是通过WeakReference引用的 。 这样做是出于性能方面的考虑,更具体地说是为了允许VM的垃圾收集器收集可能属于被终止活动的任何ImageView 。 换句话说,我们不希望活动保留其ImageView的强引用,以便可以轻松清除它们。 查看官方Android开发人员的博客文章 ,以获取有关此内容的更多信息。
关于列表活动,也必须进行一些更改。 最大的变化是原始的ArrayAdapter已由我们的自定义适配器替换。 我们用搜索结果对象的内容填充适配器,然后在适配器上调用notifyDataSetChanged方法,以通知附加的View基础数据已更改,并且应该刷新自身。 这是新实现的代码:
package com.javacodegeeks.android.apps.moviesearchapp;
import java.util.ArrayList;
import android.app.ListActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
import com.javacodegeeks.android.apps.moviesearchapp.ui.MoviesAdapter;
public class MoviesListActivity extends ListActivity {
private static final String IMDB_BASE_URL = "http://m.imdb.com/title/";
private ArrayList<Movie> moviesList = new ArrayList<Movie>();
private MoviesAdapter moviesAdapter;
@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<Movie>) 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
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Movie movie = moviesAdapter.getItem(position);
String imdbId = movie.imdbId;
if (imdbId==null || imdbId.length()==0) {
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);
}
public void longToast(CharSequence message) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
}
启动应用程序并提供搜索查询。 当“ MoviesListActivity”被触发时,您将首先看到包含电影和相应信息的列表。 慢慢地,各种图像将在下载时开始出现! 不要忘记,此操作毕竟是在后台进行的。 请注意,某些电影(尤其是较旧的电影)没有相应的缩略图。 看一下以下图片:
而已! 您可以在此处下载到目前为止创建的Eclipse项目。
翻译自: https://www.javacodegeeks.com/2010/11/android-full-app-part-6-customized-list.html