展会信息港展会大全

Android之ListView异步加载图片且仅显示可见子项中的图片
来源:互联网   发布日期:2015-11-26 13:10:25   浏览:1935次  

导读:折腾了好多天,遇到 N 多让人崩溃无语的问题,不过今天终于有些收获了,这是实验的第一版,有些混乱,下一步进行改造细分,先把代码记录在这儿吧。网上查了很多资料,发现都千篇一律,抄来抄去,很多细...

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

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

项目主要实现的功能:

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

没有涉及到下拉加载和刷新数据,目前还没接触到这些,而且已发现自定义 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" + (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() " + 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 滚动相关的事件,我一开始在这里折腾了好久,可以去看看我这篇文章:

Android 关于 OnScrollListener 事件顺序次数的简要分析

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.mThreadSizesoftReference = 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 = mFirstPosition && positionmaxHeight) {

height = maxHeight;

} else if (height

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

android 一些数据转换方法

Android实现图片宽度100%ImageView宽度且高度按比例自动伸缩

赞助本站

人工智能实验室

相关热词: android开发 教程

AiLab云推荐
推荐内容
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港