Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android實現類似ListView模式的回收和更新機制的瀑布流

Android實現類似ListView模式的回收和更新機制的瀑布流

編輯:關於Android編程

博客地址:http://blog.csdn.net/u010593680/article/details/43771857(轉載請保留原文地址)

項目地址:https://git.oschina.net/0-0Xuan/XWaterFall

分析問題:

在做項目中遇到了需要使用瀑布流的情況,於是便和往常一樣使用ScroollView模式的瀑布流,但是瀑布流效果容易實現,可一旦加載大量圖片,則一不了心就內存溢出了,而瀑布流往往需要添加大量的圖片,內存管理可以說是必要之舉,那麼問題就來了,如何在瀑布流中進行內存管理呢?

解決方法一: 使用各種ImageLoader。將圖片的Bitmap全都在ImageLoader中進行統計管理,如果圖片使用內存超過限制了,則釋放部分圖片的Bitmap,但這裡就有個顯而易見的問題,即釋放哪個圖片的bitmap?這往往是最困難的一步,很多模式采取釋放最近最少使用的Bitmap,但統計bitmap的使用本身就是一件比較難的事情,所以ImageLoader主要是用於快速高效的加載圖片

解決方法二:使用自定義View,當View出現在手機屏幕上時,WindowsManager會調用View的onDraw方法來描繪View,這樣即可實時的確定哪個View的圖片需要使用,再加載對應的圖片進內存,這樣就可最大限度減少內存的使用,這個辦法幾乎能完美解決內存溢出的問題,但是,使用該方法需要在onDraw()方法調用時,再加載圖片,那麼加載圖片的延遲將使用戶體驗大打折扣,當然也可以在代碼中添加一些聯系的代碼,當某個View顯示時,一並加載該View附近的View的圖片,但如此一來各個View的聯系就十分密切,代碼編寫難度會提高不少。

解決方法三:模擬ListView使得該瀑布流可以回收不顯示的View。知道了哪些view沒有被顯示,那麼只要回收這些View的圖片的bitmap即可。

本博客主要介紹第三種解決方法!

實現目標:一、使waterfall能夠發現正在屏幕中顯示的View,並且能夠設置滑動時,加載View的正向和反向需要緩存的View的個數,以提前加載View,使用戶體驗更好

實現目標二、使waterfall能實現類似於ListView的notifyDataSetChange()方法,使得調用notifyDataSetChange()後ListView將檢查原來的View是否有更新,如果有更新則進行相應的更新。這樣就能方便的修改、增加、刪除waterfall中的View了。


XWaterFall的使用:

第一步繼承XWaterFall,繼承後會顯示7個必需實現的方法:

/**
	 * notifyDataSetChange時調用來獲取子View,不建議在此進行大量的內存操作,如圖片bitmap的加載,應將該操作放置在onResumeView(int position, View view)中
	 * 
	 * @param position
	 * @return 返回需要添加的View
	 */
	@Override
	public View getView(int position) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * 如果能確定對應的View的高度則可在子類覆蓋該方法
	 * 
	 * @param position
	 * @return
	 */
	@Override
	public int getHeight(int position) {
		// TODO Auto-generated method stub
		return 0;
	}

	/**
	 * 返回position對應的View的標識,用以判斷View是否需要更新
	 * 
	 * @param position
	 * @return
	 */
	@Override
	public Object getMark(int position) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * 
	 * @return WaterFall中子View的數量
	 */
	@Override
	public int getContentChildCount() {
		// TODO Auto-generated method stub
		return 0;
	}

	/**
	 * 
	 * @return 一次notifyDataSetChanged()最多添加的View的個數
	 */
	@Override
	public int getNewChildNum() {
		// TODO Auto-generated method stub
		return 0;
	}

	/**
	 * 有新超出顯示屏的View時調用該方法
	 * 
	 * @param position
	 * @param view
	 */
	@Override
	protected void onRecycleView(int position, View view) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * 有新進入顯示屏的View時調用該方法
	 * 
	 * @param position
	 * @param view
	 */
	@Override
	protected void onResumeView(int position, View view) {
		// TODO Auto-generated method stub
		
	}
了解了方法的實現要求,我們現在來實現一個顯示美女和美女position的瀑布流,實現效果如下:
\

實現的代碼是:

<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHByZSBjbGFzcz0="brush:java;">public class ImageWaterFall extends XWaterFall { private String TAG = "ImageWaterFall"; private Context context; private List imgs; private LayoutInflater inflater; public ImageWaterFall(Context context, List imgs) { super(context,3,0);//設置waterfall的context、列數、寬度 // TODO Auto-generated constructor stub this.context = context; this.inflater = LayoutInflater.from(context); this.imgs = imgs; } @Override public View getView(int position) { // TODO Auto-generated method stub View view = inflater.inflate(R.layout.item_my_image, null); MyImageView iv = (MyImageView) view.findViewById(R.id.iv); iv.setName("美女"+position); TextView tv = (TextView) view.findViewById(R.id.tvName); tv.setText("美女: " + position); return view; } @Override public int getContentChildCount() { // TODO Auto-generated method stub return imgs.size(); } @Override public int getNewChildNum() { // TODO Auto-generated method stub return 12; } @Override public void onScrollToBottom() { // TODO Auto-generated method stub notifyDataSetChanged(); } @Override public Object getMark(int position) { // TODO Auto-generated method stub return imgs.get(position); } @Override protected void onRecycleView(int position, View view) { // TODO Auto-generated method stub MyImageView iv = (MyImageView) view.findViewById(R.id.iv); TextView tv = (TextView)view.findViewById(R.id.tvName); tv.setBackgroundColor(context.getResources().getColor(R.color.orange)); String url = imgs.get(position); Bitmap bm = ImageLoader.getBitmap(url); if (bm != null && !bm.isRecycled()) bm.recycle(); iv.setImageBitmap(null); ImageLoader.deleteBitmap(url); android.view.ViewGroup.LayoutParams params = iv.getLayoutParams(); params.height = iv.getHeight(); params.width = iv.getWidth(); iv.setLayoutParams(params); iv.recycle(); } @Override protected void onResumeView(int position, View view) { // TODO Auto-generated method stub TextView tv = (TextView)view.findViewById(R.id.tvName); tv.setBackgroundColor(context.getResources().getColor(R.color.green)); MyImageView iv = (MyImageView) view.findViewById(R.id.iv); ImageLoader.loadImg(imgs.get(position), iv); iv.resume(); } @Override public int getChildHeight(int position) { // TODO Auto-generated method stub return 0; } }

需要注意的是:

一、getChildHeight()中返回的是子View的高度,如果不能事先知道View的高度,則可以返回0或負數,那麼會自動使用默認的選擇最短列的算法

二、當發生View的更新和添加等操作,將會調用getView方法,但可能該View沒在顯示屏和緩沖區內,在這裡耗費大量內存顯然不合適,而當View第一次出現在顯示屏和緩沖區中時,將會調用onResumeView()方法,所以onResumeView()更適合作為加載圖片或其他非常耗費內存方法調用的地方

三、保證某個View的onResumeView()方法會在getView()之後,並且只有當View第一次出現在顯示屏和緩沖區中時才會被調用

當某個View第一次離開顯示屏和緩沖區中時,onRecycleView()會被調用

onResumeView()和onRecycleView()會被交替調用,並且保證先調用onResumeView(),才有可能調用onRecycleView()


在ImageWaterFall代碼中也進行第三點的測試:當onResumeView()和onRecycleView()被調用時,會分別調用MyImageView的resume()和recycle()

public class MyImageView extends ImageView {

	private String TAG = "MyImageView";
	private String name;
	private int state; //用來檢驗onResumeView和onRecycleView交替執行,且先onResumeView再有onRecycleView
	
	public MyImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		 this.setOnClickListener(onclick);//XWaterFall不會影響上層View的OnClickListener等操作
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public void setImageBitmap(Bitmap bm) {
		// TODO Auto-generated method stub
		// Log.v(name, "setImageBitmap");
		super.setImageBitmap(bm);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		// Log.v(name, "onDraw :" + drawNum++ );
		try {
			super.onDraw(canvas);
		} catch (Exception e) {
			e.printStackTrace();
			Log.v(TAG, name);
		}
	}

	public void recycle(){
		Log.v(TAG, "XX回收" + name);
		state--;
		if(state!= 0){
			Log.v(TAG, "XX錯誤: state:" + state);
		}
	}
	
	public void resume(){
		Log.v(TAG, "XX恢復" + name);
		state++;
		if(state != 1){
			Log.v(TAG, "XX錯誤: state:" + state);
		}
	}
	
	OnClickListener onclick = new OnClickListener() {

		@Override
		public void onClick(View v) {
			// TODO Auto-generated method stub
			Log.v(TAG, "OnClickListener");
		}
	};

}


XWaterFall的實現弄好了,接下來就是具體的使用了:

第一步:創建:ImageWaterFall iwf = new ImageWaterFall(this, imgs);

第二步:添加到某容器裡:llContent.addView(iwf);

第三步:更新ImageWaterfall中的內容:iwf.notifyDataSetChanged();

前面兩步和普通的View的使用類似,第三步則是添加ImageWaterfall子View的操作

還可以設置正反方向上緩存的數量,設置較大的緩沖數量有助於減少用戶等待查看的時間,改善用戶體驗

(手指向上滑,則准備顯示的是下方的View,所以下方為正方向,上方為反方向,手指向下滑,則准備顯示的是上方的View,所以上方為正方向,下方為反方向)

	iwf.setPositionCacheNum(18);//設置加載View時正向的緩存的View的數量
	iwf.setOppositeCacheNum(18);//設置加載View時反向的緩存的View的數量
\

接下來介紹如何更新、刪除、插入View到ImageWaterFall中:

switch(view.getId()){
		case R.id.btRefrash://更新position為1的View

			imgs.set(1, "http://g.hiphotos.baidu.com/image/pic/item/1ad5ad6eddc451da9f2e8e8cb5fd5266d11632f8.jpg");
			iwf.notifyDataSetChanged();
			break;
		case R.id.btDelete://刪除position為1的View
			imgs.remove(1);
			iwf.notifyDataSetChanged();
			break;
		case R.id.btInseart://在position為1的View前插入新View
			imgs.add(1, "http://h.hiphotos.baidu.com/image/pic/item/810a19d8bc3eb1350c58efbca41ea8d3fd1f441d.jpg");
			iwf.notifyDataSetChanged();
			break;
		}
注意上面的imgs對應的是ImageWaterFall中的private List imgs;這個用法和ListView中的用法非常相似

XWaterFall的使用就介紹完畢了,源碼在博客頂部,如有任何問題,歡迎反饋!!




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