折腾了好多天,遇到 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 滚动相关的事件,我一开始在这里折腾了好久,可以去看看我这篇文章:

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<String, SoftReference<Drawable>> mImageCaches = new HashMap<String, SoftReference<Drawable>>();    /**     * 使用线程池,根据 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<Drawable> 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>(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<String, Integer> mImagesHeight = new HashMap<String, Integer>();    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();        }    }}


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

android 一些数据转换方法

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




另外提醒下:

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


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

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


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

更多相关文章

  1. Android使用PowerImageView实现播放强大的ImageView动画效果
  2. Android实现资源动态加载的两种方式
  3. android 修改电量图标(改为数字图标)
  4. Android中图片压缩方案详解及源码下载
  5. android webview js交互之自定义错误加载界面(重新刷新)
  6. Android(安卓)drawable resource error:No resource found that m
  7. Android线程与并行,AsyncTask(AsyncTask回调方法、AsyncTask泛型参
  8. Android中利用ContentResolver获取本地音乐和相片
  9. Android(安卓)自定义View界面大合集

随机推荐

  1. android新闻应用、应用锁、小说阅读、短
  2. android绑定服务方法使用
  3. Android(安卓)Studio 导出未签名 apk
  4. Android内核开发:图解Android系统的启动过
  5. Android隐藏状态栏、导航栏
  6. ERROR:Android(安卓)Studio - “Unmappabl
  7. 关于新一代Android的一切Android(安卓)L
  8. android 的分享功能
  9. Android(安卓)ExpandableListView同时显
  10. 【Android】 Android中Log调试详解