您的位置:首页 > 其它

ListView注意的问题和源码解析(上)

2016-06-13 18:04 351 查看

设置没有数据时显示的默认布局

setEmptyView这个方法传入的是一个view,

TextView emptytext=new TextView(this);
emptytext.setText("the list is empty");
listview.setEmptyView(emptytext);
上面的代码有什么问题呢?当listview为空时,emptytext能正常显示吗?

运行测试之后会发现,emptytext没有显示,是因为没有设置大小吗?debug代码,

查看当前的root布局,子布局中怎么也找不到emptytext这个view。这个布局根本就没有加载,更别说绘制了。

通过源码可以发现setEmptyView方法只是把AdapterView的属性mEmptyView设置为emptyView

@android.view.RemotableViewMethod
public void setEmptyView(View emptyView) {
mEmptyView = emptyView;

// If not explicitly specified this view is important for accessibility.
if (emptyView != null
&& emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}

final T adapter = getAdapter();
final boolean empty = ((adapter == null) || adapter.isEmpty());
updateEmptyStatus(empty);
}


在更新数据时,在更新数据时控制mEmptyView和listVeiw的显示状态

private void updateEmptyStatus(boolean empty) {
if (isInFilterMode()) {
empty = false;
}

if (empty) {
if (mEmptyView != null) {
mEmptyView.setVisibility(View.VISIBLE);
setVisibility(View.GONE);
} else {
// If the caller just removed our empty view, make sure the list view is visible
setVisibility(View.VISIBLE);
}

// We are now GONE, so pending layouts will not be dispatched.
// Force one here to make sure that the state of the list matches
// the state of the adapter.
if (mDataChanged) {
this.onLayout(false, mLeft, mTop, mRight, mBottom);
}
} else {
if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
setVisibility(View.VISIBLE);
}
}


ListView添加删除更新header和footer

对footer和header的操作相同,这里以header为例。其实是完全相同的,最终都被封装为FixedViewInfo,只是显示的位置不同

ListView可以添加多个header

TextView header = new TextView(this);
header.setText("this is header 1");
listview.addHeaderView(header);
header = new TextView(this);
header.setText("this is header 2");
listview.addHeaderView(header);
header和footer连同adapter被包装在了HeaderViewListAdapter中,

header和footer相关view保存在了下面两个方法中

ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
ArrayList<ListView.FixedViewInfo> mFooterViewInfos;
HeaderViewListAdapter并没有提供获取FixedViewInfo的方法,因此无法获取FixedViewInfo的实例。

如果外部没有header的引用,就无法获取这个header了。这个时候要更新header就需要先移除,然后重新创建,重新添加。

重新创建是很浪费内存的,如果header的数据要更新,还是在外部持有引用直接更新比较好。

header的内容不是原始adapter中的数据,header数据变化不需要通知数据更新。外部持有引用可直接修改内容,不需要通知adapter刷新数据。

listView中多种类型布局使用

BaseAdapter中默认是一种类型布局,支持多种类型布局需要在自定义的Adapter中重写这两个方法

public int getViewTypeCount() {
return 1;
}
public int getItemViewType(int position) {
return 0;
}


getViewTypeCount返回布局类型个数。getItemViewType返回某个位置的布局类型。

这个地方容易出现数组越界的问题。getItemViewType的返回值是不可以随便写的。它的最大值不超过TypeCount-1,

否则就会数组越界。

这是因为在复用多种布局时,TypeCount是布局类型最大个数,而ItemViewType是一个下标,

这个下标的最大值就是TypeCount-1。

/**
* Unsorted views that can be used by the adapter as a convert view.
*/
private ArrayList<View>[] mScrapViews;

public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//noinspection unchecked
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}


View getScrapView(int position) {
final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
return null;
}
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else if (whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
return null;
}


OnItemClickListener中获取获取点击的item数据

public void onItemClick(AdapterView<?> parent, View view, int position, long id)

这个方法的postiton并不是真实的item数据位置,当没有header时,positon与item位置对应

当有header时,positon=headercount+item位置,也就是说

item位置=positon-headercount

listview.getHeaderViewsCount()可以获取headercount

还有一种方法是通过包装过的adapter直接获取

parent.getAdapter().getItem(position);

异步加载图片混乱

当滑动ListView的时候,图片自动变来变去,图片显示的位置也不正确。

所有的主流图片加载库都解决了这个问题。这个问题的原因是listview的item复用。

解决这个问题的思路都是建立当前imageview与加载的url的对应关系,使当前看到的item始终加载的是当前对应数据。

Picasso通过建立ImageView与ImageViewAction的的对应关系来加载正确图片

void enqueueAndSubmit(Action action) {
Object target = action.getTarget();
if (target != null && targetToAction.get(target) != action) {
// This will also check we are on the main thread.
cancelExistingRequest(target);
targetToAction.put(target, action);
}
submit(action);
}


RecycleBin机制

View复用方式

public static final class ViewHolder {
public ImageView img;
public TextView name;

public ViewHolder(View convertView) {
img = (ImageView) convertView.findViewById(R.id.img);
name = (TextView) convertView.findViewById(R.id.name);
}
}


@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(ListViewActivity.this).inflate(R.layout.item_list, parent, false);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.name.setText(data.get(position));
return convertView;
}


源码解析

单独再做分析吧

ListView中的设计模式

ListView与Adapter的应用就是典型的适配器模式

ListView与Adapter都实现了接口ListAdapter,同时ListView又包裹了Adapter,从而实现适配,是典型的对象适配。

装饰模式

HeaderViewListAdapter是典型的装饰模式

对Adapter进行装饰,为Adapter扩展了footer和header

数据刷新观察者模式

在设置Adapter时会构建一个AdapterDataSetObserver,这就是创建观察者

adapter中包含一个数据集可观察者DataSetObservable,这就是可观察者

adapter的notifyDataSetChanged就是可观察者通知观察者数据发生了变化。

其它常见小问题:

点击item没反应

原因是tem子控件获取了焦点,在Item布局的根布局加上android:descendantFocusability=“blocksDescendants”

的属性就可以了。

descendantFocusability值含义:

beforeDescendants:viewgroup会优先其子类控件而获取到焦点

afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点

blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

给ListView加上背景图片,或者背景颜色时,滚动时listView会黑掉

原因是,滚动时,列表里面的view重绘时,用的依旧是系统默认的透明色,颜色值为#FF191919,

要改变这种情况,只需要调用listView的setCacheColorHint(0),颜色值设置为0

或者xml文件中listView的属性 Android:cacheColorHint="#00000000"即可。

ListView设置item高度无效

在item的layout文件中,用android:layout_height设置item的高度。运行,高度设置无效。

解决办法:给item设定minHeight,即可。

设置虚线分割线

android:divider="@drawable/dash_line"
dash_line为drawable

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line">
<!-- 显示一条虚线,破折线的宽度为dashWith,破折线之间的空隙的宽度为dashGap,当dashGap=0dp时,为实线 #d6dadd -->
<stroke
android:width="0.5dp"
android:color="#D6DADD"
android:dashGap="2dp"
android:dashWidth="2dp" />
</shape>


欢迎扫描二维码,关注公众号




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: