Android ListView
專門用于處理那種內容元素很多,手機屏幕無法展示出所有內容的情況。ListView可以使用列表的形式來展示內容,超出屏幕部分的內容只需要通過手指滑動就可以移動到屏幕內了。
ListView屬性
設置分割線
- android:divider,設置分割線風格,可以是顏色,也可以是圖片。當不需要分割線時,賦值@null即可。
- android:dividerHeight,設置分割線高度。
滾動條設置
android:scrollbars,通過該屬性可以設置滾動條狀態。不需要滾動條時,賦值none。
取消Item點擊效果
android:listSelector,賦值為#00000000(color ARGB),或者@android:color/transparent。
設置ListView Item顯示位置
默認顯示第一個,調用
ListView.setSelection(pos)從指定item開始顯示。
但是此方法是瞬間完成滾動操作,可以使用以下三種方法實現平滑滾動:
- smoothScrollBy(distance, duration),指定滾動距離,distance的正,負決定滾動的方向(正值向上,負值向下)。滾動速度有duration決定,即滾動時間。
- smoothScrollByOffset(offset),方法參數是指現在顯示的第一個item視圖的偏移量,負號代表向上移動,正號代表向下移動.
- smoothScrollToPosition(pos),平滑移動到指定的position item.
ListView基礎用法
動態修改ListView
在某些情況下,ListView的數據更新了,那么就需要動態的修改ListView item的顯示??梢哉{用Adapter.notifyDataSetChanged()方法。代碼如下:
mList.add("new");
mAdapter.notifyDataSetChanged();
mList是BaseAdapter的數據。更新它之后調用notifyDataSetChanged()更新視圖。注意調用notifyDataSetChanged()之后是更新了整個ListView的所有item視圖,也就是重新繪制了ListView,所以效率有點差,可以試試單獨更新某個item。
遍歷item
當需要遍歷ListView的item時,可以使用getFirstVisiblePosition(),getChildAt(),getChildCount()等方法。不過需要注意:
- getFirstVisiblePosition(),返回的是當前屏幕上顯示的第一個item(包括不完整的)在所有item中的位置index。
- getChildCount(),返回當前屏幕顯示的item數量。
- getChildAt(),返回指定位置的item視圖。指定位置的范圍不能超過當前屏幕顯示的數量,因為返回的視圖是在當前顯示的item中的。
通過以上三種方法,更進一步了解了視圖緩存機制。ListView并沒有給所有數據項創建item視圖,只給需要顯示的創建。
item.getTop()
當通過item視圖調用getTop()時,返回的坐標是以item左上角點在以ListView左上角為原點構建的坐標系的Y坐標。
空ListView
當列表沒有數據時,可以設置一個提示用戶的View。通過ListView.setEmptyView()來設置。
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_empty_list_view);
initView();
}
private void initView() {
mListView = (ListView) findViewById(R.id.id_list_view_empty);
mEmptyImg = (ImageView) findViewById(R.id.img_empty_view);
mListView.setEmptyView(mEmptyImg);
mList = new ArrayList<>();
mAdapter = new ViewHolderAdapter(mList, this);
mListView.setAdapter(mAdapter);
}
public void btnAddItem(View view) {
mList.add("new");
mAdapter.notifyDataSetChanged();
mListView.smoothScrollToPosition(mList.size() - 1);
}
布局代碼
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/id_list_view_empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<ImageView
android:id="@+id/img_empty_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@mipmap/ic_launcher"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="btnAddItem"
android:text="@string/add_item"/>
</LinearLayout>
效果:
滑動監聽
OnTouchListener
mListView.setOnTouchListener(new View.OnTouchListener() {
int position = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//觸摸時操作
break;
case MotionEvent.ACTION_MOVE:
//移動時操作
break;
case MotionEvent.ACTION_UP:
//手指離開時操作
//用getTop()方法獲取到的坐標是相對于父控件坐標系的坐標.
position = mListView.getChildAt(0).getTop();
mView.setText("顯示的第一個Item在ListView中的坐標:" + position);
break;
}
return false;
}
});
OnScrollListener
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
int pos = 0;
int lastVisibleItemPos = 0;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch(scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
//滑動停止時
pos = mListView.getChildAt(0).getTop();
mTextView.setText("顯示的第一個Item在ListView中的坐標:" + pos);
break;
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
//正在滾動時
mTextView.setText("正在滑動...");
break;
case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
//手指拋動時,手指用力滑動后,手指離開屏幕ListView由于慣性繼續滑動
mTextView.setText("漂移中...");
break;
default:
break;
}
}
//滾動時一直調用
//firstVisibleItem當前顯示的第一個item在所有item中的index
//visibleItemCount當前顯示的item數量
//totalItemCount所有item 數量
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//判斷滑動方向
if(firstVisibleItem > lastVisibleItemPos) {
//判斷是否滾動到最后一項
if(firstVisibleItem + visibleItemCount == totalItemCount
&& totalItemCount > 0) {
Log.d(TAG, "滾動到了最后一個item");
}
Log.d(TAG, "上滑");
}else if(firstVisibleItem < lastVisibleItemPos) {
Log.d(TAG, "下滑");
}
lastVisibleItemPos = firstVisibleItem;
}
});
ListView點擊事件
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// lastScrollY = mListView.getScrollY();
// Log.d(TAG, lastScrollY + "");
lastScrollY = getScrollY();
Log.d(TAG, lastScrollY + "");
mTextView.setText(String.valueOf(lastScrollY));
Intent intent = new Intent(RestoreListView.this, JumpActivity.class);
startActivity(intent);
mListView.setSelection(0);
}
});
ListView基礎優化(ViewHolder)
優化的原理是基于ListView的RecycleBin機制
ViewHolder的優化就是利用了ListView 的視圖緩沖機制,一般情況下代碼都差不多,關鍵在于BaseAdapter。所以可以參照以下模版來寫。
BaseAdapter代碼
public class ViewHolderAdapter extends BaseAdapter {
private List<String> mData;
private LayoutInflater mInflater;
public ViewHolderAdapter(List<String> data, Context context) {
mData = data;
mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
//判斷是否緩存
if (convertView == null) {
holder = new ViewHolder();
//通過LayoutInflater實例化布局
convertView = mInflater.inflate(R.layout.viewholder_item, null);
holder.img = (ImageView) convertView.findViewById(R.id.img_view_holder);
holder.title = (TextView) convertView.findViewById(R.id.tv_view_holder);
convertView.setTag(holder);
} else {
//通過Tag找到緩存布局
holder = (ViewHolder) convertView.getTag();
}
//設置布局中空間要顯示的視圖
holder.img.setBackgroundResource(R.mipmap.ic_launcher);
holder.title.setText(mData.get(position));
return convertView;
}
public class ViewHolder {
public ImageView img;
public TextView title;
}
}
代碼分析,在上面的模版中看到覆寫了幾個方法:
-
getCount()
,返回item數量 -
getItem(int position)
,返回指定位置的item -
getItemId(int position)
,返回指定位置item的行號 -
getView()
,返回顯示的item view
整個優化的關鍵在于ViewHolder模式充分利用了ListView的視圖緩存機制,避免每一次調用getView()時都去實例化item布局,并且調用findViewById()實例化控件。
ListView進階用法
ListView位置恢復
有兩種方法,第一種是在監聽ListView時,通過ListView.getScrollY()方法獲取最終滾動的Y坐標,然后調用smoothScrollBy()方法恢復;第二種是通過記錄當前ListView顯示的第一個Item的index,調用smoothToPosition()來恢復。第一中方法完全不可行,因為得到的永遠都是0(通過ListView調用getScrollY()方法,得到的坐標是ListView左上角在以ListView父視圖左上角為原點構建的坐標系中的Y軸坐標,所以一直是0)。第二種方法不精確。
下面采用以下方法:
public int getScrollY() {
View child = mListView.getChildAt(0);
if(child == null) {
return 0;
}
int top = -child.getTop();
int firstVisibleItemPos = mListView.getFirstVisiblePosition();
return top + firstVisibleItemPos * child.getHeight();
}
當然這是一種理想狀態,默認為所有item的高度都相等。
恢復ListView
mListView.post(new Runnable() {
@Override
public void run() {
mListView.smoothScrollBy(scrolledY, 0);
}
});
動態改變ListView布局
有兩種方案:一種是兩種布局都寫,通過控制布局的顯示隱藏來達到切換布局的效果;另一種是在getView()時通過判斷來選擇加載不同的布局。以下主要以第二種方案來實現。
關鍵思想,要獲取不同的布局肯定要覆寫getView()方法。而ListView提供了兩種方法,封裝好了布局的判斷:
- getItemViewType(),獲取指定位置item的布局類型。
- getViewTypeCount(),獲取不同布局的總數。
關鍵代碼
@Override
public int getItemViewType(int position) {
ChatItemListViewBean bean = mData.get(position);
return bean.getType();
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if(convertView == null) {
holder = new ViewHolder();
if(getItemViewType(position) == 0) {
convertView = mInflater.inflate(R.layout.chat_item_in, null);
holder.mIcon = (ImageView) convertView.findViewById(R.id.id_img_chat_item_in);
holder.mTv = (TextView) convertView.findViewById(R.id.id_tv_chat_item_in);
}else {
convertView = mInflater.inflate(R.layout.chat_item_out, null);
holder.mIcon = (ImageView) convertView.findViewById(R.id.id_img_chat_item_out);
holder.mTv = (TextView) convertView.findViewById(R.id.id_tv_chat_item_out);
}
convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
holder.mIcon.setImageBitmap(mData.get(position).getIcon());
holder.mTv.setText(mData.get(position).getText());
return convertView;
}
public class ViewHolder{
public ImageView mIcon;
public TextView mTv;
}
效果:
雜技
獲取ActionBar高度
getResources().getDimensionPixelOffset(
android.support.v7.appcompat.R.dimen.abc_action_bar_default_height_material
)
android.support.v7.appcompat.R.dimen.abc_action_bar_default_height_material是V7包內的actionBar height 的資源id。
獲取最小滑動距離
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
問題
- 如何實現單獨更新某個指定的item數據
- View.post()
- 如何實現彈性ListView
- 如何自動隱藏和顯示Toolbar
- View.getTranslationY()獲取到的是什么坐標
- ListView的適配器,BaseAdapter,ArrayAdapter...