Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android之ListView異步加載圖片且僅顯示可見子項中的圖片

Android之ListView異步加載圖片且僅顯示可見子項中的圖片

編輯:關於Android編程

折騰了好多天,遇到 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 < 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> 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();
        }
    }
}


代碼相關的一些類方法,以及涉及到的其他方面問題的相關博文:

android 一些數據轉換方法

Android實現圖片寬度100%ImageView寬度且高度按比例自動伸縮


n塊ズ椐鷌源碼:http://yunpan.cn/QpzhBEWCw3gDH

項目中需要修改服務端的地址,我在壓縮包中附有一些 服務端發送的 JSON 數據


還可以加入 Android 文件共享群,這裡全是 Android 和 JAVA 學習資料和教程:http://qun.yunpan.360.cn/38063538

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved