Android(安卓)ListView
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。
通过add添加新数据.gif遍历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); }
布局代码
效果:
空ListView显示图像,通过add添加新数据.gif滑动监听
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; } });
TouchListener,底部TextView显示第一个item Y轴坐标.gif 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; } });
OnSrollListener,底部TextViw根据不同滑动状态显示不同的内容,停止滑动是显示第一个item Y轴坐标.gif 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机制
可以参考Android ListView工作原理完全解析,带你从源码的角度彻底理解
ViewHolder的优化就是利用了ListView 的视图缓冲机制,一般情况下代码都差不多,关键在于BaseAdapter。所以可以参照以下模版来写。
BaseAdapter代码
public class ViewHolderAdapter extends BaseAdapter { private List mData; private LayoutInflater mInflater; public ViewHolderAdapter(List 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位置恢复.gif 动态改变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; }
效果:
聊天.png杂技
获取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...
参考
判断ListView的第一个item是否完全显示
记录和恢复ListView的滑动位置
对于getScrollX() 的理解
更多相关文章
- Android(安卓)对话框(Dialog)大全 建立你自己的对话框
- Android中设置theme无效
- Android(安卓)阿拉伯语言适配
- Android工程目录结构简介
- Android中的各种Adapter
- android 游戏学习(jbox2d)
- android 点击空白出隐藏软键盘
- Android(安卓)内存监测工具 DDMS --> Heap
- Android(安卓)自定义时间选择对话框