Android Full App,第6部分:用于数据演示的自定义列表视图

这是“ Android完整应用程序教程”系列的第六部分。 完整的应用程序旨在提供一种通过互联网搜索表演电影/演员的简便方法。 在本系列的第一部分( “主要活动UI” )中,我们创建了Eclipse项目并为应用程序的主要活动设置了基本界面。 在第二部分( “使用HTTP API” )中,我们使用Apache HTTP客户端库来使用外部HTTP API并将API的搜索功能集成到我们的应用程序中。 在第三部分( “解析XML响应” )中,我们看到了如何使用Android内置的XML解析功能来解析XML响应。 在第四部分( “从主要活动异步执行API请求” )中,我们将HTTP检索器和XML解析器服务绑定在一起,以便从应用程序的主要活动中执行API搜索请求。 为了避免阻塞主UI线程,该请求是在后台线程中异步执行的。 在第五部分( “使用意图启动新活动” )中,我们了解了如何启动新的活动以及如何将数据从一个活动转移到另一个活动 。 在这一部分中,我们将创建一个自定义列表视图,以提供更好的数据可视化表示。

在我们的教程中用来显示搜索结果的视图非常简单。 如果您还记得的话,结果将传递到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 (将保留缩略图)和另一个LinearLayoutLinearLayout是许多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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值