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...