編輯:關於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; } }*/ }
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 HashMapmImagesHeight = 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寬度且高度按比例自動伸縮
源碼:http://yunpan.cn/QpzhBEWCw3gDH
項目中需要修改服務端的地址,我在壓縮包中附有一些 服務端發送的 JSON 數據
“什麼是spring aop?”,我頓時大腦浮想聯翩,我想到了事物管理、SDK、代碼監控、spring remoting,這麼多東西,我從哪裡回答
由於項目上的需要側滑條目展示收藏按鈕,記得之前代碼家有寫過一個厲害的開源控件 AndroidSwipeLayout 本來准備直接拿來使用,但是看過 issue 發現現在有
這幾天無聊,於是做了個隨機器,想著以後可能會用的到。以下是程序的運行截圖 圖1可以通過右上角的菜單轉到圖2 的Activity 。這個程序提供了兩個功能,一個是
(1)主要用了paint ,canvas 兩個類中的方法 (2)主要用了畫線和畫圓的方法。 (3)drawline(起始點軸坐標,起始點y軸坐標,終點軸坐標,終點y軸坐標