博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android之ListView异步加载图片且仅显示可见子项中的图片
阅读量:5031 次
发布时间:2019-06-12

本文共 40992 字,大约阅读时间需要 136 分钟。

折腾了好多天,遇到 N 多让人崩溃无语的问题,不过今天终于有些收获了,这是实验的第一版,有些混乱,下一步进行改造细分,先把代码记录在这儿吧。

网上查了很多资料,发现都千篇一律,抄来抄去,很多细节和完整实例都没看到,只有自己一点点研究了,总体感觉 android 下面要显示个图片真不容易啊。

项目主要实现的功能:

  1. 异步加载图片
  2. 图片内存缓存、异步磁盘文件缓存
  3. 解决使用 viewHolder 后出现的图片错位问题
  4. 优化列表滚动性能,仅显示可见子项中的图片
  5. 无需固定图片显示高度,对高度进行缓存使列表滚动时不会因图片高度变化而闪动,使滚动体验更加流畅
  6. 图片动画展示效果,新加载的图片显示透明渐变动画

没有涉及到下拉加载和刷新数据,目前还没接触到这些,而且已发现自定义 ListView 中如果有添加 顶部和底部 的拉动加载更多数据提示的 view ,将会导致 ListView 的 child 数量和 position 混乱,所以只有先简单使用 ListView 来做个效果。

核心主要是三个文件:MainActivity.java,  ZAsyncImageLoader.java, DiaryListAdapter.java

下面贴代码:

MainActivity.java

package com.ai9475.meitian;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.nio.charset.Charset;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.HashMap;import java.util.Iterator;import android.content.Intent;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.drawable.Drawable;import android.net.Uri;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Looper;import android.support.v4.app.FragmentTransaction;import android.support.v7.app.ActionBar;import android.support.v7.app.ActionBarActivity;import android.util.JsonReader;import android.util.Log;import android.view.Menu;import android.view.View;import android.widget.AbsListView;import android.widget.AdapterView;import android.widget.ImageView;import android.widget.ListView;import android.widget.SimpleAdapter;import android.widget.TextView;import android.widget.Toast;import com.ai9475.meitian.adapter.DiaryListAdapter;import com.ai9475.util.ZAsyncImageLoader;import com.ai9475.util.ZHttpRequest;import com.ai9475.util.ZLog;import com.ai9475.widget.PullToRefreshListView;import org.apache.http.entity.ContentType;import org.apache.http.entity.mime.MultipartEntityBuilder;import org.apache.http.protocol.HTTP;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import org.json.JSONStringer;import org.w3c.dom.Text;public class MainActivity extends ActionBarActivity{    private static final String TAG = "MainActivity";    private ListView mDiaryListView;    private DiaryListAdapter mDiaryListAdapter;    private ZAsyncImageLoader mAsyncImageLoader;    private Handler handler = new Handler();    private int endId = 0;    private boolean isScrolling = false;    @Override    protected void onCreate(Bundle savedInstanceState)    {        Log.d("main activity", "start");        // 执行父级初始化方法        super.onCreate(savedInstanceState);        // 让 ActionBar 浮动在 Activity 上方进行半透明遮盖        //this.supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);        // 解析视图数据        this.setContentView(R.layout.activity_main);        AppManager.getInstance().addActivity(this);        this.mAsyncImageLoader = new ZAsyncImageLoader();        this.mAsyncImageLoader.setIsUseDiskCache(true);        this.mAsyncImageLoader.setCacheDir(AppConfig.IMAGE_CACHE_PATH);        // 配置 ActionBar 相关        final ActionBar bar = this.getSupportActionBar();        // 标题        bar.setTitle("Bar");        // 返回按钮        //bar.setDisplayHomeAsUpEnabled(true);        // 应用徽标控制        //bar.setDisplayUseLogoEnabled(false);        // 应用图标控制        //bar.setDisplayShowHomeEnabled(true);        // 标题栏控制        //bar.setDisplayShowTitleEnabled(true);        // 设置 TABS 导航模式        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);        /*        bar.getHeight();        final ScrollView scrollView = (ScrollView) findViewById(R.id.scrollView);        ViewTreeObserver scvto = scrollView.getViewTreeObserver();        if (scvto != null) {            scvto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {                @Override                public boolean onPreDraw() {                    scrollView.setPadding(                            scrollView.getPaddingLeft(),                            bar.getHeight(),                            scrollView.getPaddingRight(),                            scrollView.getPaddingBottom()                    );                    return true;                }            });        }*/        /*Fragment fragmentA = new FragmentTab();        Fragment fragmentB = new FragmentTab();        Fragment fragmentC = new FragmentTab();        tabA.setTabListener(new MyTabsListener(fragmentA));        tabB.setTabListener(new MyTabsListener(fragmentB));        tabC.setTabListener(new MyTabsListener(fragmentC));*/        bar.addTab(bar.newTab().setText("ATab").setTabListener(new MyTabsListener()));        bar.addTab(bar.newTab().setText("BTab").setTabListener(new MyTabsListener()));        bar.addTab(bar.newTab().setText("CTab").setTabListener(new MyTabsListener()));        /*//bar.setDisplayShowHomeEnabled(false);        //bar.setDisplayShowTitleEnabled(false);        // 顶部帧布局操作栏        final FrameLayout topActBar = (FrameLayout) findViewById(R.id.topActionBar);        // 底部帧布局操作栏        final FrameLayout bottomActBar = (FrameLayout) findViewById(R.id.bottomActionBar);        // 列表滚动视图        final ScrollView scrollView = (ScrollView) findViewById(R.id.scrollView);        // 顶部操作栏绑定事件:同步设置滚动视图顶部内边距        topActBar                .getViewTreeObserver()                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {                    @Override                    public boolean onPreDraw() {                        scrollView.setPadding(                                scrollView.getPaddingLeft(),                                topActBar.getHeight(),                                scrollView.getPaddingRight(),                                scrollView.getPaddingBottom()                        );                        return true;                    }                });        // 底部操作栏绑定事件:同步设置滚动视图底部内边距        bottomActBar                .getViewTreeObserver()                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {                    @Override                    public boolean onPreDraw() {                        scrollView.setPadding(                                scrollView.getPaddingLeft(),                                scrollView.getPaddingTop(),                                scrollView.getPaddingRight(),                                bottomActBar.getHeight()                        );                        return true;                    }                });        */        //AppContext context = (AppContext) getApplicationContext();        //context.test();/*        ZAsyncImageLoader loader = new ZAsyncImageLoader();        String url1 = "http://img.ai9475.com/data/attachment/images/meitian/c5/e4/59/c5e459f00dce21480c9941eefbb88f90_200.jpg";        String url2 = "http://img.ai9475.com/data/attachment/images/meitian/f9/29/ee/f929ee1dd6af7b805744b9fb3f4f99b5_200.jpg";        loader.loadDrawable(url1, new ZAsyncImageLoader.OnImageLoadListener() {            @Override            public void onLoaded(Drawable imageDrawable, String imageUrl) {                ImageView img = (ImageView) findViewById(R.id.showPic1);                img.setImageDrawable(imageDrawable);            }        });        loader.loadDrawable(url2, new ZAsyncImageLoader.OnImageLoadListener() {            @Override            public void onLoaded(Drawable imageDrawable, String imageUrl) {                ImageView img = (ImageView) findViewById(R.id.showPic2);                img.setImageDrawable(imageDrawable);            }        });*/        // 找到日记列表视图对象        this.mDiaryListView = (ListView) findViewById(R.id.diaryListCt);        new Thread(){            @Override            public void run() {                Runnable runnable = new Runnable() {                    @Override                    public void run() {                        loadDiaryListData();                    }                };                handler.post(runnable);            }        }.start();    }    /**     * 日记列表初始化     */    protected void initDiaryList(JSONArray diaryList)    {        Log.d("initDiaryList", "start");        // 列表单元与数据的适配器生成        this.mDiaryListAdapter = new DiaryListAdapter(this, this.mDiaryListView, this.mAsyncImageLoader, diaryList);        // 绑定列表数据单元适配器        Log.d("initDiaryList", "setAdapter");        this.mDiaryListView.setAdapter(this.mDiaryListAdapter);        Log.d("bindListViewEvents", "start");        // 绑定日记列表事件        this.bindListViewEvents();        Log.d("DiaryListAdapter", "end");    }    static int j = 0;    /**     * 绑定日记列表事件     */    public void bindListViewEvents()    {        // 列表滚动事件        this.mDiaryListView.setOnScrollListener(new AbsListView.OnScrollListener(){            @Override            public void onScrollStateChanged(AbsListView absListView, int scrollState)            {                switch (scrollState) {                    case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:                        ZLog.i(TAG, "OnScrollListener : SCROLL_STATE_TOUCH_SCROLL");                        mDiaryListAdapter.setIsSCrolling(true);                        break;                    case AbsListView.OnScrollListener.SCROLL_STATE_FLING:                        ZLog.i(TAG, "OnScrollListener : SCROLL_STATE_FLING");                        mDiaryListAdapter.setIsSCrolling(true);                        break;                    case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:                        // 第一个可见 item 的 position                        int first = mDiaryListView.getFirstVisiblePosition();                        // 最后一个可见 item 的 position                        int last = mDiaryListView.getLastVisiblePosition();                        // 屏幕上可见 item 的总数                        int onScreenCount = mDiaryListView.getChildCount();                        int total = first + last;                        ZLog.i(TAG, "OnScrollListener : SCROLL_STATE_IDLE => " + (j++) +", first: "+ first +", last: "+ last +", total: "+ total +", onScreenCount:"+ onScreenCount);                        mDiaryListAdapter.setIsSCrolling(false);                        mDiaryListAdapter.setPositionRange(first, last);                        View child;                        int position;                        for (int i = 0; i < onScreenCount; i++) {                            position = first + i;                            if (mDiaryListAdapter.isInPrevPositionRange(position)) {                                ZLog.i(TAG, "inPrevPositionRange position:"+ position);                                continue;                            }                            // 获取可见 item 子项的视图容器对象                            child = mDiaryListView.getChildAt(i);                            ImageView picPhoto = (ImageView) child.findViewById(R.id.picPhoto);                            ImageView avatar = (ImageView) child.findViewById(R.id.avatar);                            try {                                ZLog.i(TAG, "load image i:"+ first);                                mDiaryListAdapter.loadImage(picPhoto, avatar, mDiaryListAdapter.getItem(position));                            } catch (JSONException e) {                                AppException.io(e);                            }                        }                        break;                    default:                        break;                }            }            @Override            public void onScroll(AbsListView absListView, int first, int last, int total) {                //mDiaryListAdapter.setPositionLimit(first, last);                //ZLog.i(TAG, "OnScrollListener : onScroll => " + (j++) +", first: "+ first +", last: "+ last +", total:"+ total);            }        });        // 列表单元点击事件        ZLog.i(TAG, "diaryListInit : setOnItemClickListener");        this.mDiaryListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {            @Override            public void onItemClick(AdapterView
adapterView, View view, int i, long l) { getSupportActionBar().setTitle("点击了: "+ i); } }); ZLog.i("DiaryListAdapter", "setOnRefreshListener"); // 当向下拉动刷新时触发列表更新事件 /*this.mDiaryListView.setOnRefreshListener(new PullToRefreshListView.OnRefreshListener() { @Override public void onRefresh() { getSupportActionBar().setTitle("执行加载…"); loadDiaryListData(); mDiaryListView.onRefreshComplete(); } });*/ } public void loadDiaryListData() { ZLog.i(TAG, "loadDiaryListData : start"); try { ZHttpRequest httpRequset = new ZHttpRequest(new ZHttpRequest.OnHttpRequestListener() { @Override public void onRequest(ZHttpRequest request) { ZLog.i(TAG, "request data : start"); } @Override public void onSucceed(int statusCode, ZHttpRequest request) { // 创建每行数据的集合 ZLog.i(TAG, "request onSucceed : start"); try { String content = request.getInputStream(); if (content == null) { Toast.makeText(getApplicationContext(), "数据请求失败", Toast.LENGTH_SHORT).show(); return; } JSONArray diaryList = new JSONArray(content); /*if (asyncImageLoader.getMaxPosition() < 1) { asyncImageLoader.setPositionLimit(0, diaryList.length()); }*/ endId = ((JSONObject) diaryList.opt(diaryList.length() - 1)).getInt("id"); initDiaryList(diaryList); } catch (IOException e) { Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show(); } catch (JSONException e) { Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show(); } } @Override public void onFailed(int statusCode, ZHttpRequest request) { ZLog.i(TAG, "request onFailed : code"+ statusCode); } }); httpRequset.get("http://m.ai9475.com/?con=meitian_app&endId=" + this.endId); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); } } protected class MyTabsListener implements ActionBar.TabListener {// private Fragment fragment;// public MyTabsListener(Fragment fragment)// {// this.fragment = fragment;// } @Override public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {// ft.add(R.id.fragmentPlace, this.fragment, null); } @Override public void onTabReselected(ActionBar.Tab arg0, FragmentTransaction arg1) { // TODO Auto-generated method stub } @Override public void onTabUnselected(ActionBar.Tab arg0, FragmentTransaction arg1) { // TODO Auto-generated method stub } } /** * 配置 ActionBar * * @param menu * @return */ public boolean onCreateOptionsMenu(Menu menu) { this.getMenuInflater().inflate(R.menu.main, menu); return super.onCreateOptionsMenu(menu); } /*public void doClick(View view) { ZHttpRequest get = new ZHttpRequest(); get .setCharset(HTTP.UTF_8) .setConnectionTimeout(5000) .setSoTimeout(5000); get.setOnHttpRequestListener(new ZHttpRequest.OnHttpRequestListener() { @Override public void onRequest(ZHttpRequest request) throws Exception { } @Override public String onSucceed(int statusCode, ZHttpRequest request) throws Exception { return request.getInputStream(); } @Override public String onFailed(int statusCode, ZHttpRequest request) throws Exception { return "GET 请求失败:statusCode "+ statusCode; } }); ZHttpRequest post = new ZHttpRequest(); post .setCharset(HTTP.UTF_8) .setConnectionTimeout(5000) .setSoTimeout(10000); post.setOnHttpRequestListener(new ZHttpRequest.OnHttpRequestListener() { private String CHARSET = HTTP.UTF_8; private ContentType TEXT_PLAIN = ContentType.create("text/plain", Charset.forName(CHARSET)); @Override public void onRequest(ZHttpRequest request) throws Exception { // 设置发送请求的 header 信息 request.addHeader("cookie", "abc=123;456=爱就是幸福;"); // 配置要 POST 的数据 MultipartEntityBuilder builder = request.getMultipartEntityBuilder(); builder.addTextBody("p1", "abc"); builder.addTextBody("p2", "中文", TEXT_PLAIN); builder.addTextBody("p3", "abc中文cba", TEXT_PLAIN); if (picPath != null && ! "".equals(picPath)) { builder.addTextBody("pic", picPath); builder.addBinaryBody("file", new File(picPath)); } request.buildPostEntity(); } @Override public String onSucceed(int statusCode, ZHttpRequest request) throws Exception { return request.getInputStream(); } @Override public String onFailed(int statusCode, ZHttpRequest request) throws Exception { return "POST 请求失败:statusCode "+ statusCode; } }); TextView textView = (TextView) findViewById(R.id.showContent); String content = "初始内容"; try { if (view.getId() == R.id.doGet) { content = get.get("http://www.baidu.com"); content = "GET数据:isGet: " + (get.isGet() ? "yes" : "no") + " =>" + content; } else { content = post.post("http://192.168.1.6/test.php"); content = "POST数据:isPost" + (post.isPost() ? "yes" : "no") + " =>" + content; } } catch (IOException e) { content = "IO异常:" + e.getMessage(); } catch (Exception e) { content = "异常:" + e.getMessage(); } textView.setText(content); } public void doPhoto(View view) { destoryBimap(); String state = Environment.getExternalStorageState(); if (state.equals(Environment.MEDIA_MOUNTED)) { Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); startActivityForResult(intent, 1); } else { Toast.makeText(MainActivity.this, "没有SD卡", Toast.LENGTH_LONG).show(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Uri uri = data.getData(); if (uri != null) { this.photo = BitmapFactory.decodeFile(uri.getPath()); } if (this.photo == null) { Bundle bundle = data.getExtras(); if (bundle != null) { this.photo = (Bitmap) bundle.get("data"); } else { Toast.makeText(MainActivity.this, "拍照失败", Toast.LENGTH_LONG).show(); return; } } FileOutputStream fileOutputStream = null; try { // 获取 SD 卡根目录 String saveDir = Environment.getExternalStorageDirectory() + "/meitian_photos"; // 新建目录 File dir = new File(saveDir); if (! dir.exists()) dir.mkdir(); // 生成文件名 SimpleDateFormat t = new SimpleDateFormat("yyyyMMddssSSS"); String filename = "MT" + (t.format(new Date())) + ".jpg"; // 新建文件 File file = new File(saveDir, filename); // 打开文件输出流 fileOutputStream = new FileOutputStream(file); // 生成图片文件 this.photo.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream); // 相片的完整路径 this.picPath = file.getPath(); ImageView imageView = (ImageView) findViewById(R.id.showPhoto); imageView.setImageBitmap(this.photo); } catch (Exception e) { e.printStackTrace(); } finally { if (fileOutputStream != null) { try { fileOutputStream.close(); } catch (Exception e) { e.printStackTrace(); } } } } /** * 销毁图片文件 * private void destoryBimap() { if (photo != null && ! photo.isRecycled()) { photo.recycle(); photo = null; } }*/}

其中涉及到 scroll 滚动相关的事件,我一开始在这里折腾了好久,可以去看看我这篇文章:

ZAsyncImageLoader.java

package com.ai9475.util;import android.graphics.drawable.Drawable;import android.os.Handler;import android.os.Message;import java.io.DataInputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.lang.ref.SoftReference;import java.net.HttpURLConnection;import java.net.URL;import java.util.Date;import java.util.HashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 异步多线程加载图片 * * Created by ZHOUZ on 14-2-7. */public class ZAsyncImageLoader{    private static final String TAG = "ZAsyncImageLoader";    /**     * 线程池中的线程数量     */    private int mThreadSize = 5;    /**     * 是否使用 SD 卡缓存图片     */    private boolean mIsUseDiskCache = false;    /**     * SD 卡上缓存的图片有效期(单位:秒)     */    private int mExpireTime = 86400;    /**     * 图片缓存文件目录     */    private String mCachePath = null;    /**     * 同步缓存已加载过的图片,使用软引用优化内存     */    private HashMap
> mImageCaches = new HashMap
>(); /** * 使用线程池,根据 CPU 数量来动态决定可用线程数量 */ private ExecutorService mExecutorService = null; /** * 设置 SD 卡中的图片缓存有效时长(单位:秒) * * @param time */ public void setExpireTime(int time) { this.mExpireTime = time; } /** * 设置线程数量 * * @param size */ public void setThreadSize(int size) { this.mThreadSize = size; } /** * 设置是否使用 SD 卡缓存图片 * * @param isUse */ public void setIsUseDiskCache(Boolean isUse) { this.mIsUseDiskCache = isUse; } /** * 设置缓存目录 * * @param path */ public void setCacheDir(String path) { this.mCachePath = path; } /** * 获取线程池管理器 * * @return */ public ExecutorService getExecutorService() { if (this.mExecutorService == null) { if (this.mThreadSize < 1) { this.mThreadSize = Runtime.getRuntime().availableProcessors() * 5; } this.mExecutorService = Executors.newFixedThreadPool(this.mThreadSize); } return this.mExecutorService; } /** * 加载图片的多线程控制 * * @param imageUrl * @param tag * @param listener */ public Drawable loadDrawable(final String imageUrl, final String tag, final OnImageLoadListener listener) { // 是否已缓存过图片, 是则从缓存中直接获取, 若缓存中数据丢失则重新远程加载 if (this.mImageCaches.containsKey(imageUrl)) { SoftReference
softReference = this.mImageCaches.get(imageUrl); if (softReference != null) { Drawable drawable = softReference.get(); if (drawable != null) { return drawable; } } } // 异步多线程加载图片后的数据传递处理 final Handler handler = new Handler() { @Override public void handleMessage(Message message) { if (message.what == 1) { listener.onLoaded((Drawable) message.obj, imageUrl, tag); } else { listener.onFailed((IOException) message.obj, imageUrl, tag); } } }; // 通过线程池来控制管理图片加载 this.getExecutorService().submit(new Runnable() { @Override public void run() { Message msg; try { Drawable drawable = loadImageFromUrl(imageUrl); mImageCaches.put(imageUrl, new SoftReference
(drawable)); msg = handler.obtainMessage(1, drawable); } catch (IOException e) { msg = handler.obtainMessage(0, e); } handler.sendMessage(msg); } }); return null; } /** * 加载远程图片或本地图片缓存文件 * * @param imageUrl * @return * @throws IOException */ public Drawable loadImageFromUrl(String imageUrl) throws IOException { // 检查 SD 卡是否可用并将图片缓存到 SD 卡上 if (mIsUseDiskCache && mCachePath != null) { File d = new File(mCachePath); if (! d.exists()) { d.mkdirs(); } final File f = new File(mCachePath + ZHelper.md5(imageUrl)); long time = (new Date()).getTime(); long expire = time - (mExpireTime * 1000L); // 文件存在且在有效期内则直接读取 if (f.exists() && f.lastModified() > expire) { FileInputStream fis = new FileInputStream(f); return Drawable.createFromStream(fis, "src"); } // 远程加载图片后写入到 SD 卡上 InputStream i = this.getImageInputStream(imageUrl); if (i == null) { return null; } final Drawable drawable = Drawable.createFromStream(i, "src"); // 将图片异步写入到本地 SD 卡中缓存, 避免阻塞UI线程, 导致图片不能显示 new Thread(new Runnable() { @Override public void run() { try { InputStream i = ZFormat.drawable2InputStream(drawable); DataInputStream in = new DataInputStream(i); FileOutputStream out = new FileOutputStream(f); byte[] buffer = new byte[1024]; int byteRead; while ((byteRead = in.read(buffer)) != -1) { out.write(buffer, 0, byteRead); } in.close(); out.close(); } catch (IOException e) { ZLog.d("write image cache IOException", e.getMessage()); e.printStackTrace(); } } }).start(); return drawable; } // 只读取远程图片不缓存 else { InputStream i = this.getImageInputStream(imageUrl); return Drawable.createFromStream(i, "src"); } } /** * 远程加载图片数据 * * @param imageUrl * @return * @throws IOException */ public InputStream getImageInputStream(String imageUrl) throws IOException { URL m = new URL(imageUrl); HttpURLConnection conn = (HttpURLConnection) m.openConnection(); conn.setRequestMethod("GET"); conn.setUseCaches(false); conn.setDoInput(true); conn.setConnectTimeout(5000); conn.setReadTimeout(30000); conn.setInstanceFollowRedirects(true); return conn.getInputStream(); } /** * 加载图片的事件监听器 */ public interface OnImageLoadListener { /** * 图片加载完成事件处理 * * @param imageDrawable * @param imageUrl * @param tag */ public void onLoaded(Drawable imageDrawable, String imageUrl, String tag); /** * 图片加载失败的事件处理 * * @param e * @param imageUrl * @param tag */ public void onFailed(IOException e, String imageUrl, String tag); } protected void finalize() { this.mExecutorService.shutdown(); }}

DiaryListAdapter.java

package com.ai9475.meitian.adapter;import android.content.Context;import android.graphics.drawable.Drawable;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.view.animation.AlphaAnimation;import android.widget.AbsListView;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.ListView;import android.widget.RelativeLayout;import android.widget.TextView;import android.widget.Toast;import com.ai9475.meitian.R;import com.ai9475.util.ZAsyncImageLoader;import com.ai9475.util.ZHelper;import com.ai9475.util.ZLog;import com.ai9475.util.ZUI;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;import java.util.Map;/** * Created by ZHOUZ on 14-2-8. */public class DiaryListAdapter extends BaseAdapter implements AbsListView.RecyclerListener{    private static final String TAG = "DiaryListAdapter";    private Context mContext;    private ListView mDiaryListView;    public View mConvertView;    private ZAsyncImageLoader mAsyncImageLoader;    private JSONArray mDiaryDataList = null;    private boolean mIsScrolling = false;    private int mFirstPosition = 0;    private int mLastPosition = 0;    private int mPrevFirstPosition = 0;    private int mPrevLastPosition = 0;    private HashMap
mImagesHeight = new HashMap
(); public DiaryListAdapter(Context context, ListView listView, ZAsyncImageLoader imageLoader, JSONArray diaryList) { this.mContext = context; this.mAsyncImageLoader = imageLoader; this.mDiaryDataList = diaryList; this.mDiaryListView = listView; } public void setIsSCrolling(boolean flag) { this.mIsScrolling = flag; } /** * 当前列表加载到的日记总数 * * @return */ public int getCount() { return this.mDiaryDataList == null ? 0 : this.mDiaryDataList.length(); } /** * 可见单元位置对比是否处在在上次滚动可是范围内 * * @param position * @return */ public boolean isInPrevPositionRange(int position) { // 初始化时直接返回 false if (this.mPrevLastPosition == 0) return false; // 检测当前 item 的位置是否在上次滚动范围内, 是则表示该 item 正处于屏幕可见状态中无需重新加载 return (position >= this.mPrevFirstPosition && position <= this.mPrevLastPosition) ? true : false; } /** * 设置滚动后可见的起止项目序号 * * @param first * @param last */ public void setPositionRange(int first, int last) { // 保存上次滚动后的可见位置 this.mPrevFirstPosition = this.mFirstPosition; this.mPrevLastPosition = this.mLastPosition; // 重置当前可见位置 this.mFirstPosition = first; this.mLastPosition = last; ZLog.i(TAG, "setPositionLimit prevFirst: "+ mPrevFirstPosition +", prevLast: "+ mPrevLastPosition +", first: "+ mFirstPosition +", last: "+ mLastPosition); } /** * 获取当前列表单元的日记id * * @param position * @return */ public long getItemId(int position) { int id = 0; try { id = this.getItem(position).getInt("id"); } catch (JSONException e) { Toast.makeText(this.mContext, e.getMessage(), Toast.LENGTH_LONG); } return id; } /** * 获取一条数据 * * @param position * @return */ public JSONObject getItem(int position) { return (JSONObject) this.mDiaryDataList.opt(position); } /** * 获取视图 * * @param position * @param convertView * @param parent * @return */ public View getView(int position, View convertView, ViewGroup parent) { ZLog.v(TAG, "getView i: " + position); final ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(this.mContext).inflate(R.layout.list_item_diary, null); this.mConvertView = convertView; holder = new ViewHolder(convertView); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } try { // 获取当前列表单元的 JSON 日记数据 JSONObject item = this.getItem(position); ZLog.i(TAG, "getView i: "+ position +", isScrolling: "+ mIsScrolling +", mFirstPosition: "+ mFirstPosition +", mLastPosition: "+ mLastPosition); /*if (! isScrolling && (position >= mFirstPosition && position <= mLastPosition)) { ZLog.i(TAG, "getView i: "+ position +", show images"); this.loadImage(holder.picPhoto, holder.avatar, item); } else {*/ ZLog.i(TAG, "getView i: "+ position +", can't show images"); // 初始化时自动加载 if (this.mLastPosition == 0) { this.loadImage(holder.picPhoto, holder.avatar, item); this.mPrevLastPosition = position; } else { this.setDefaultImage(holder.picPhoto, holder.avatar, item); } /*holder.picPhoto.setScaleType(ImageView.ScaleType.CENTER); holder.picPhoto.setImageResource(R.drawable.default_pic); holder.avatar.setScaleType(ImageView.ScaleType.CENTER); holder.avatar.setImageResource(R.drawable.default_avatar); }*/ holder.nickname.setText(item.getString("nickname") +":"+ position); holder.content.setText(item.getString("content")); holder.calendarMonth.setText(ZHelper.dateFormat("MM月", item.getInt("calendarDate"))); holder.calendarDay.setText(ZHelper.dateFormat("dd", item.getInt("calendarDate"))); } catch (JSONException e) { Toast.makeText(this.mContext, e.getMessage(), Toast.LENGTH_LONG); } return convertView; } public void setDefaultImage(ImageView picPhoto, ImageView avatar, JSONObject item) throws JSONException { int height = 0; String picUrl = getPicUrl(item.getString("picUrl")); if (mImagesHeight.containsKey(picUrl)) { height = mImagesHeight.get(picUrl); } int minHeight = ZUI.dp2px(this.mContext, 100); if (height < minHeight) height = minHeight; picPhoto.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height)); picPhoto.setScaleType(ImageView.ScaleType.CENTER); picPhoto.setImageResource(R.drawable.default_pic); avatar.setScaleType(ImageView.ScaleType.CENTER); avatar.setImageResource(R.drawable.default_avatar); } public String getPicUrl(String pic) { return "http://img.ai9475.com/data/attachment/images/meitian/" + pic; } public String getAvatarUrl(String avatar) { return "http://img.ai9475.com/data/attachment/images/avatar/" + avatar; } /** * 加载可见单元的图片 * * @param picPhoto * @param avatar * @param item * @throws JSONException */ public void loadImage(ImageView picPhoto, ImageView avatar, JSONObject item) throws JSONException { // 图片链接 String picUrl = getPicUrl(item.getString("picUrl")); String avatarUrl = getAvatarUrl(item.getString("avatar")); // 记录异步加载的图片标签 String picTag = "pic"+ item.getInt("calendarDate") + item.getInt("id"); String avatarTag = "avatar"+ item.getInt("id"); picPhoto.setTag(picTag); avatar.setTag(avatarTag); OnPicLoadListener mOnPicLoadListener = new OnPicLoadListener(); OnAvatarLoadListener mOnAvatarLoadListener = new OnAvatarLoadListener(); // 异步加载远程日记照片或缓存 Drawable picDrawable = this.mAsyncImageLoader.loadDrawable(picUrl, picTag, mOnPicLoadListener); // 存在缓存则使用缓存中的图片资源或者使用默认占位图 mOnPicLoadListener.setDrawable(picPhoto, picUrl, picTag, picDrawable); // 异步加载远程用户头像或加载缓存 Drawable avatarDrawable = this.mAsyncImageLoader.loadDrawable(avatarUrl, avatarTag, mOnAvatarLoadListener); // 存在缓存则使用缓存中的图片资源或者使用默认占位图 mOnAvatarLoadListener.setDrawable(avatar, avatarUrl, avatarTag, avatarDrawable); } /** * 当列表单元滚动到可是区域外时清除掉已记录的图片视图 * * @param view */ @Override public void onMovedToScrapHeap(View view) { /*ViewHolder holder = (ViewHolder) view.getTag(); this.imageViews.remove(holder.avatar); this.imageViews.remove(holder.picPhoto);*/ } private static class ViewHolder { public ImageView picPhoto; public ImageView avatar; public TextView nickname; public TextView content; public TextView calendarMonth; public TextView calendarDay; public ViewHolder(View view) { this.picPhoto = (ImageView) view.findViewById(R.id.picPhoto); this.avatar = (ImageView) view.findViewById(R.id.avatar); this.nickname = (TextView) view.findViewById(R.id.nickname); this.content = (TextView) view.findViewById(R.id.content); this.calendarMonth = (TextView) view.findViewById(R.id.calendarMonth); this.calendarDay = (TextView) view.findViewById(R.id.calendarDay); } } /** * 头像图片加载事件监听 */ private class OnAvatarLoadListener extends OnImageLoadListener { private int mImageSource = R.drawable.default_avatar; /** * 设置图片 * * @param view * @param imageUrl * @param tag * @param drawable */ public void setDrawable(ImageView view, String imageUrl, String tag, Drawable drawable) { if (view == null) return; if (drawable != null) { view.setScaleType(ImageView.ScaleType.CENTER_CROP); view.setImageDrawable(drawable); } else { view.setScaleType(ImageView.ScaleType.CENTER); view.setImageResource(this.mImageSource); } } } /** * 日记照片加载事件监听 */ private class OnPicLoadListener extends OnImageLoadListener { private int mImageSource = R.drawable.default_pic; /** * 设置图片 * * @param view * @param imageUrl * @param tag * @param drawable */ public void setDrawable(ImageView view, String imageUrl, String tag, Drawable drawable) { if (view == null) return; int height = 0; if (mImagesHeight.containsKey(imageUrl)) { height = mImagesHeight.get(imageUrl); } if (drawable != null) { // 定义图片的最佳高度 if (height == 0) { int minHeight = ZUI.dp2px(mContext, 100); int maxHeight = ZUI.dp2px(mContext, 300); height = (int) ((float) view.getWidth() / drawable.getMinimumWidth() * drawable.getMinimumHeight()); if (height > maxHeight) { height = maxHeight; } else if (height < minHeight) { height = minHeight; } mImagesHeight.put(imageUrl, height); } // 现将图片完全透明 drawable.setAlpha(0); view.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height)); view.setScaleType(ImageView.ScaleType.CENTER_CROP); view.setImageDrawable(drawable); // 添加透明渐变动画显示图片 AlphaAnimation alphaAnim = new AlphaAnimation(0.0f, 1.0f); alphaAnim.setDuration(1000); view.setAnimation(alphaAnim); } else { int minHeight = ZUI.dp2px(mContext, 100); height = height < minHeight ? minHeight : height; view.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height)); view.setScaleType(ImageView.ScaleType.CENTER); view.setImageResource(mImageSource); } } } /** * 图片的加载监听事件 */ abstract private class OnImageLoadListener implements ZAsyncImageLoader.OnImageLoadListener { /** * 实现图片显示的抽象方法 * * @param view * @param tag * @param drawable */ abstract public void setDrawable(ImageView view, String imageUrl, String tag, Drawable drawable); @Override public void onLoaded(Drawable drawable, String imageUrl, String tag) { ImageView view = (ImageView) mDiaryListView.findViewWithTag(tag == null ? imageUrl : tag); this.setDrawable(view, imageUrl, tag, drawable); } @Override public void onFailed(IOException e, String imageUrl, String tag) { //Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show(); } }}

代码相关的一些类方法,以及涉及到的其他方面问题的相关博文:

另外提醒下:

如果遇到 position 值对应不上,可能是你使用了自定义的 ListView 中又添加拉动加载更多的子项,导致ListView中的 child 数量和 getView 中传递的 position 对应不上,我在这里折腾了好久才偶然发现这个问题。

项目源码:http://yunpan.cn/QpzhBEWCw3gDH

项目中需要修改服务端的地址,我在压缩包中附有一些 服务端发送的 JSON 数据

还可以加入 Android 文件共享群,这里全是 Android 和  JAVA 学习资料和教程:http://qun.yunpan.360.cn/38063538

转载于:https://www.cnblogs.com/zhouzme/p/5758504.html

你可能感兴趣的文章
设计模式のCompositePattern(组合模式)----结构模式
查看>>
二进制集合枚举子集
查看>>
磁盘管理
查看>>
SAS学习经验总结分享:篇二—input语句
查看>>
UIImage与UIColor互转
查看>>
RotateAnimation详解
查看>>
系统管理玩玩Windows Azure
查看>>
c#匿名方法
查看>>
如何判断链表是否有环
查看>>
【小程序】缓存
查看>>
ssh无密码登陆屌丝指南
查看>>
MySQL锁之三:MySQL的共享锁与排它锁编码演示
查看>>
docker常用命令详解
查看>>
jQuery技巧大放送
查看>>
字符串转换成JSON的三种方式
查看>>
Hive时间函数笔记
查看>>
clojure-emacs-autocomplete
查看>>
一个自己写的判断2个相同对象的属性值差异的工具类
查看>>
10 华电内部文档搜索系统 search03
查看>>
[HIHO1149]回文字符序列(dp)
查看>>