Android ListView 全面优化

结合昨天学习的多线程,今天又继续对ListView进行了优化,包括异步加载图片,滑动时暂停加载,滑动停止后再加载显示界面中的item。

综合ListView在使用时参考的多篇博客,这里对ListView的使用进行全面的优化总结。

1. ListView 主界面

package com.panasonic.imagelight;

import com.panasonic.imagelight.adapter.AsyncImageLoader;
import com.panasonic.imagelight.adapter.MovieListAdapter;
import com.panasonic.imagelight.data.MovieList;
import com.panasonic.imagelight.utils.MoviePlayerActivity;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;

public class MainListFragment extends Fragment implements OnItemClickListener, AbsListView.OnScrollListener {

    private final String TAG = this.getClass().getName().toString();
    private View view;
    private TextView noMovieTextView;
    private ListView movieListView;
    private MovieListAdapter mAdapter;

    private AsyncImageLoader asyncImageLoader = new AsyncImageLoader();
    int start;
    int end;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.main_list_fragment, container, false);
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        noMovieTextView = (TextView) view.findViewById(R.id.movie_list_no_movie_tv);
        movieListView = (ListView) view.findViewById(R.id.movie_list_fragment_lv);
        if (MovieList.getInstance().getArrayList().size() == 0) {
            noMovieTextView.setVisibility(View.VISIBLE);
        } else {
            setListView();
        }
    }

    private void setListView() {
        mAdapter = new MovieListAdapter(getActivity(), movieListHandler, asyncImageLoader, start, end);
        movieListView.setAdapter(mAdapter);
        movieListView.setOnItemClickListener(this);
        movieListView.setOnScrollListener(this);
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int onScroll) {
        switch (onScroll) {
            case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                // 手指触屏拉动准备滚动,只触发一次        顺序: 1
                asyncImageLoader.lock();
                break;
            case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
                // 持续滚动开始,只触发一次                顺序: 2
                asyncImageLoader.lock();
                break;
            case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
                // 整个滚动事件结束,只触发一次            顺序: 4
                asyncImageLoader.unlock();
                asyncImageLoader.setStartEnd(movieListView.getFirstVisiblePosition(), movieListView.getLastVisiblePosition());
                break;
            default:
                break;
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        // 一直在滚动中,多次触发                          顺序: 3
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        String file_path = MovieList.getInstance().getArrayList().get(position).file_path;
        Intent intent = new Intent(getActivity(), MoviePlayerActivity.class);
        intent.putExtra("file_path", file_path);
        startActivity(intent);
        Log.i(TAG, "onItemClick");
    }

    public Handler movieListHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            refreshListView();
        }
    };

    public void refreshListView() {
        if (mAdapter != null) {
            mAdapter.notifyDataSetChanged();
        } else {
            noMovieTextView.setVisibility(View.GONE);
            setListView();
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        ((ViewGroup) view.getParent()).removeView(view);
    }

}

2. BaseAdapter

package com.panasonic.imagelight.adapter;

import com.panasonic.imagelight.R;
import com.panasonic.imagelight.data.MovieList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.io.File;

public class MovieListAdapter extends BaseAdapter {
    private Context context;
    private String record_image_path;
    private Handler movieListHandler;

    int start;
    int end;

    private AsyncImageLoader asyncImageLoader;

    public MovieListAdapter(Context context, Handler movieListHandler, AsyncImageLoader asyncImageLoader, int start, int end) {
        this.context = context;
        this.movieListHandler = movieListHandler;

        this.asyncImageLoader = asyncImageLoader;
        this.start = start;
        this.end = end;

        if (record_image_path == null) {
            record_image_path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Panasonic/";
        }
    }

    @Override
    public int getCount() {
        return MovieList.getInstance().getArrayList().size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final ViewHolder holder;

        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(R.layout.movie_list_item, parent, false);
            holder = new ViewHolder();
            holder.movie_image_iv = (ImageView) convertView.findViewById(R.id.movie_list_item_image_iv);
            holder.movie_name_tv = (TextView) convertView.findViewById(R.id.movie_list_item_name_tv);
            holder.total_time_tv = (TextView) convertView.findViewById(R.id.movie_list_item_total_time_tv);
            holder.movie_size_tv = (TextView) convertView.findViewById(R.id.movie_list_item_size_tv);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        String imagePath = record_image_path + MovieList.getInstance().getArrayList().get(position).movie_name + ".png";
        File file = new File(imagePath);
        if (file.exists()) {
            // 1. Load Bitmap from SDCard
//            holder.movie_image_iv.setImageBitmap(BitmapFactory.decodeFile(imagePath));

            // 2. Load Bitmap from threadPool
            holder.movie_image_iv.setTag(position);
            loadImage(convertView, imagePath, position);
        } else {
            holder.movie_image_iv.setImageResource(R.drawable.loading);
            movieListHandler.sendEmptyMessage(0);
        }

        holder.movie_name_tv.setText(MovieList.getInstance().getArrayList().get(position).display_name);

        int sec = Integer.valueOf(MovieList.getInstance().getArrayList().get(position).total_time) / 1000 % 60;
        int min = Integer.valueOf(MovieList.getInstance().getArrayList().get(position).total_time) / 1000 / 60;
        holder.total_time_tv.setText(String.format("%02d:%02d", min, sec));

        String movieSize = String.valueOf(Integer.valueOf(MovieList.getInstance().getArrayList().get(position).movie_size) / 1000 / 1000)
                + context.getResources().getString(R.string.movie_list_fragment_size);
        holder.movie_size_tv.setText(movieSize);

        return convertView;
    }

    private void loadImage(final View convertView, String imagePath, final int position) {
        Bitmap cacheImage = asyncImageLoader.loadBitmap(imagePath, position, new AsyncImageLoader.ImageCallback() {

            @Override
            public void imageLoaded(Bitmap imageBitmap) {
                if ((convertView.findViewWithTag(position)) != null)
                    ((ImageView) convertView.findViewWithTag(position)).setImageBitmap(imageBitmap);
//                ((ImageView) convertView.findViewById(id)).setImageBitmap(imageBitmap);
                Log.i("test", "1count: " + position);
            }
        });

        if (cacheImage != null) {
            ((ImageView) convertView.findViewWithTag(position)).setImageBitmap(cacheImage);
//            ((ImageView) convertView.findViewById(id)).setImageBitmap(cacheImage);
            Log.i("test", "2count: " + position);
        } else {
            ((ImageView) convertView.findViewWithTag(position)).setImageResource(R.drawable.loading);
//            ((ImageView) convertView.findViewById(id)).setImageResource(R.drawable.loading);
        }
    }

    private class ViewHolder {
        public ImageView movie_image_iv;
        public TextView movie_name_tv;
        public TextView total_time_tv;
        public TextView movie_size_tv;
    }

}

3. AsyncImageLoader异步加载图片

package com.panasonic.imagelight.adapter;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by fansen on 2016/02/23.
 */
public class AsyncImageLoader {
    // 为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
    public Map<String, SoftReference<Bitmap>> imageCache = new HashMap<>();
    private ExecutorService executorService = Executors.newFixedThreadPool(5);
    private Handler handler = new Handler();

    private boolean firstLoad = true;
    private boolean allow = true;
    private int startLoad;
    private int endLoad;

    public void setStartEnd(int start, int end){
        startLoad = start;
        endLoad = end;
        Log.i("test", "startLoad = " + startLoad);
        Log.i("test", "endLoad = " + endLoad);
    }

    public void lock() {
        allow = false;
        firstLoad = false;
    }

    public void unlock() {
        Log.i("test", "allow = true");
        allow = true;
    }

    /**
     * @param imagePath 图像路径
     * @param callback  回调接口
     * @return 返回内存中缓存的图像,第一次加载返回null
     */
    public Bitmap loadBitmap(final String imagePath, final int position, final ImageCallback callback) {

        // 如果缓存过就从缓存中取出数据
        if (imageCache.containsKey(imagePath)) {
            SoftReference<Bitmap> softReference = imageCache.get(imagePath);
            if (softReference.get() != null) {
                Log.i("test", "AsyncImageLoader: ----2----");
                return softReference.get();
            }
        }

        // 缓存中没有图像,则从存储卡中取出数据,并将取出的数据缓存到内存中
        executorService.submit(new Runnable() {

            @Override
            public void run() {
                while (!allow){
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if (firstLoad || (position >= startLoad && position <= endLoad)) {
                    // 测试时,模拟网络延时,实际时这行代码不能有
                    SystemClock.sleep(2000);

                    final Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
                    imageCache.put(imagePath, new SoftReference<>(bitmap));

                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            Log.i("test", "AsyncImageLoader: ----1----");
                            callback.imageLoaded(bitmap);
                        }
                    });
                }
            }
        });
        return null;
    }

    //对外界开放的回调接口
    public interface ImageCallback {
        //注意 此方法是用来设置目标对象的图像资源
        void imageLoaded(Bitmap imageBitmap);

    }
}
时间: 2024-10-09 20:23:10

Android ListView 全面优化的相关文章

Android ListView性能优化实战方案

前言: 对于ListView,大家绝对都不会陌生,只要是做过Android开发的人,哪有不用ListView的呢? 只要是用过ListView的人,哪有不关心对它性能优化的呢? 关于如何对ListView进行性能优化,不仅是面试中常常会被问到的(我前段时间面试了几家公司,全部都问到了这个问题了),而且在实际项目中更是非常重要的一环,它甚至在某种程度上决定了用户是否喜欢接受你的APP.(如果你的列表滑起来很卡,我敢说很多人会直接卸载) 网上关于如何对ListView进行性能优化,提出了很多方案.但

Android ListView 的优化

一.复用convertView,降低findViewById的次数 1.优化一:复用convertView Android系统本身为我们考虑了ListView的优化问题.在复写的Adapter的类中,比較重要的两个方法是getCount()和getView().界面上有多少个条显示.就会调用多少次的getView()方法:因此假设在每次调用的时候,假设不进行优化.每次都会使用View.inflate(-.)的方法,都要将xml文件解析,并显示到界面上,这是很消耗资源的:由于有新的内容产生就会有旧

Android Listview 性能优化

首先我一般使用的适配器是BaseAdapter,其中有两个方法最主要,分别是: getCount,getView, 在对Listview 进行优化的时候,首先使用 convertview 和viewHolder 配合进行优化,使用convertview的母的是 控件复用,从而加到减少内存的使用,使用viewHolder 的是减少findbyid 的次数. 但是在进行控件以后,在进行图片加载的时候,会出现图片错位的问题,这是因为控件里面有上次残留的图片在里面,所以我们在初始化的时候需要设置一张默认

(翻译) Android ListView 性能优化指南

本文翻译了Lucas Rocha的Performance Tips for Android’s ListView.这是一篇关于介绍如何提升ListView性能的文章,非常的优秀.使得我拜读之后,忍不住将其翻译.本文采用了意译的翻译方式,尽可能的保持原文中要表达的内容.但是,任有几处翻译存在一些异议.请读者原谅.如果你对文章的内容有兴趣,请移步到我的blog,地址如下: 地址: http://kohoh1992.github.io/PerformanceTipsForAndroidListView

android listView性能优化

package com.example.ex_000_example; import java.util.ArrayList; import android.os.Bundle; import android.app.Activity; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.View; import android.vie

Android ListView复杂列表优化实践

原文:Android ListView复杂列表优化实践 很多社交App都不免会涉及到复杂的列表元素实现,一个列表上面可能大量的图片,不定长的评论列表,给手机端的程序员带来了不少的挑战.本文就是在实现复杂的列表滑动的情况下,利用已知的优化方法指导下的一次优化实践,旨在提升ListView的滑动流畅度,为用户带来良好的体验. 1:设计稿: 这是列表中可能出现的ItemView,有两种,但是又有许多相同的地方,比如一样有点赞的图片,评论等...其中,评论和点赞的数量是可变的. 2:使用一般布局带来的问

Android ListView使用BaseAdapter与ListView的优化 (转至 http://www.open-open.com/lib/view/open1339485728006.html)

在ListView的使用中,有时候还需要在里面加入按钮等控件,实现单独的操作.也就是说,这个ListView不再只是展示数据,也不仅仅是这一行要来处理用户的操作,而是里面的控件要获得用户的焦点.读者可以试试用SimpleAdapter添加一个按钮到ListView的条目中,会发现可以添加,但是却无法获得焦点,点击操作被ListView的Item所覆盖.这时候最方便的方法就是使用灵活的适配器BaseAdapter了. ▲图4-35 BaseAdapter中的方法 使用BaseAdapter必须写一

Android ListView使用BaseAdapter与ListView的优化

在ListView的使用中,有时候还需要在里面加入按钮等控件,实现单独的操作.也就是说,这个ListView不再只是展示数据,也不仅仅是这一行要来处理用户的操作,而是里面的控件要获得用户的焦点.读者可以试试用SimpleAdapter添加一个按钮到ListView的条目中,会发现可以添加,但是却无法获得焦点,点击操作被ListView的Item所覆盖.这时候最方便的方法就是使用灵活的适配器BaseAdapter了. ▲图4-35 BaseAdapter中的方法 使用BaseAdapter必须写一

ListView常用优化技巧(Android群英传)

内容是博主照着书敲出来的,博主码字挺辛苦的,转载请注明出处,后序内容陆续会码出. 前言:ListView--列表,它作为一个非常重要的显示方式,不管是在Web中还是移动平台中,都是一个非常好的.不开或缺的展示信息的工具.在Android中,ListView控件接管了这一重担,在大量的场合下,我们都需要使用这个控件.虽然在Android 5.X时代,RecyclerView在很多地方都在逐渐取代ListView,但ListView的使用范围依然非常的广泛,它这万年老大哥的地位也不是轻易就能撼动的.