1. 程式人生 > >千萬不要在ScrollView中巢狀ListView

千萬不要在ScrollView中巢狀ListView

為什麼不要在ScrollView中巢狀ListView

網上關於ScrollView巢狀ListView的文章很多。確實ListView可以通過重寫onMeasure方法將ListView的高度重新設定實現功能,但是真的得不償失。如果真的有需求請用LinearLayout代替ListView。

下面是我測試重寫onMeasure方法的ListView載入100條資料的示例:

public class MyListView extends ListView {

	public MyListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public MyListView(Context context) {
		super(context);
	}

	public MyListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	/**
	 * 重寫該方法,達到使ListView適應ScrollView的效果
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
				MeasureSpec.AT_MOST);
		super.onMeasure(widthMeasureSpec, expandSpec);
	}
}

public class MainActivity extends Activity {

	MyListView listView;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		listView = (MyListView) findViewById(R.id.list_view);
		listView.setAdapter(new MyAdapter());
	}

	class MyAdapter extends BaseAdapter {

		@Override
		public int getCount() {
			return 100;
		}

		@Override
		public Object getItem(int position) {
			return null;
		}

		@Override
		public long getItemId(int position) {
			return 0;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			
			if(convertView == null) {
				Log.i("getView", "position=" + position);
				convertView = getLayoutInflater().inflate(R.layout.city, null);
			}
			return convertView;
		}
	}
}

我只是貼出了部分關鍵程式碼,看一下log輸出。

log圖片

發現沒有,載入100條資料,getView方法就執行了100回。也就是說,佈局檔案解析了100次,實際情況下findview也會執行100次,自定義ViewHolder、convertView.setTag全沒用了,ListView的快取機制完全用不上,而且還重寫了ListView,寫了那麼多程式碼,得不償失啊。

奉勸各位不要再在ScrollView裡面巢狀ListView了,真的太傻了。

LinearLayout代替ListView的方法

將佈局檔案中使用ListView的地方替換為LinearLayout,設定android:orientation="vertical",這樣就可以通過LinearLayout的addView()方法在程式碼中新增子項。

可以使用for迴圈的方式迴圈新增子view到LinearLayout中,只是要記得每次重新載入時記得呼叫LinearLayout的removeAllViews()方法將裡面的子項清空再新增就行。

大概程式碼是這個樣子,程式碼是別人發的圖片:

LinearLayout替換ListView原始碼圖片

2014.9.9 思考

使用LinearLayout替代ListView會失去setAdapter()和notifyDataSetChanged()方法,以後想要替換佈局也不太方便。

使用這兩種方法都不能使用ListView的快取機制,如果item太多的話,可能會出現記憶體溢位的情況,就算不溢位,估計程式也會很卡,使用者體驗不好。如果是使用ListView,突然想把ListView換成GridView,只要在佈局檔案裡面替換掉,程式碼中把ListView改成GridView就好了,使用LinearLayout就明顯不好替換。

如果item過多的話,必須使用類似ListView的快取機制,我想可以在ListView中使用多種佈局,利用getViewTypeCount()和getItemViewType(int position)實現;如果佈局很簡單,甚至可以把ListView上面的內容設定為ListView的Header。