ListView
,包含一张图片、标题和一些文本。图片在单独的线程中加载(使用了队列等),当图片下载完成时,我现在调用列表适配器上的 notifyDataSetChanged()
来更新该图片。这样做是有效的,但由于 notifyDataSetChanged()
调用了所有可见项目的 getView()
,因此 getView()
被频繁地调用,我只想更新列表中的单个项目。如何才能实现呢?我对当前方法的问题有:
- 滚动速度很慢
- 当加载单个新图像时,我有一个淡入动画出现在该图像上。
ListView
,包含一张图片、标题和一些文本。图片在单独的线程中加载(使用了队列等),当图片下载完成时,我现在调用列表适配器上的 notifyDataSetChanged()
来更新该图片。这样做是有效的,但由于 notifyDataSetChanged()
调用了所有可见项目的 getView()
,因此 getView()
被频繁地调用,我只想更新列表中的单个项目。如何才能实现呢?感谢您的信息Michelle,我找到了答案。
您确实可以使用View#getChildAt(int index)
获得正确的视图。但是要注意的是它从第一个可见项开始计数。事实上,您只能获取可见项。您可以使用ListView#getFirstVisiblePosition()
来解决这个问题。
例如:
private void updateView(int index){
View v = yourListView.getChildAt(index -
yourListView.getFirstVisiblePosition());
if(v == null)
return;
TextView someText = (TextView) v.findViewById(R.id.sometextview);
someText.setText("Hi! I updated you manually!");
}
这个问题在Google I/O 2010上被提出,你可以在这里观看:
基本上,Romain Guy解释了要在ListView
上调用getChildAt(int)
来获取视图,然后(我认为)调用getFirstVisiblePosition()
来找出位置和索引之间的关联。
Romain还指出了一个名为Shelves的项目作为示例,我想他可能是指ShelvesActivity.updateBookCovers()
方法,但我找不到调用getFirstVisiblePosition()
的地方。
即将推出令人惊喜的更新:
RecyclerView将在不久的将来解决这个问题。正如http://www.grokkingandroid.com/first-glance-androids-recyclerview/所指出的那样,您将能够调用方法来精确地指定更改,例如:
void notifyItemInserted(int position)
void notifyItemRemoved(int position)
void notifyItemChanged(int position)
您的项目(行)必须具有唯一的ID,以便稍后可以更新它们。当列表从适配器获取视图时,请设置每个视图的标记。(如果默认标记在其他地方使用,则还可以使用关键标记)
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = super.getView(position, convertView, parent);
view.setTag(getItemId(position));
return view;
}
对于每个列表元素进行更新检查,如果存在指定ID的视图,则显示它并执行更新操作。
private void update(long id)
{
int c = list.getChildCount();
for (int i = 0; i < c; i++)
{
View view = list.getChildAt(i);
if ((Long)view.getTag() == id)
{
// update view
}
}
}
相比其他方法,这实际上更容易处理id而不是位置。并且,你必须调用更新函数以便让可见的项目得到更新。
SampleModel golbalmodel=new SchedulerModel();
并将其初始化为全局变量
通过将其初始化为全局模型,获取视图的当前行
SampleModel data = (SchedulerModel) sampleList.get(position);
golbalmodel=data;
将更改后的值设置为全局模型对象方法set,并添加notifyDataSetChanged,这对我很有效。
golbalmodel.setStartandenddate(changedate);
notifyDataSetChanged();
注:该问题与在Android中动态添加元素到ListView有关。CursorAdapter
的情况添加一个想法。CursorAdapter
(或ResourceCursorAdapter
、SimpleCursorAdapter
),那么你可以实现ViewBinder
或覆盖bindView()
和newView()
方法,这些方法不会在参数中接收当前列表项索引。因此,当一些数据到达并且您想要更新相关的可见列表项时,如何知道它们的索引呢?newView()
中添加项目notifyDatasetChanged()
和刷新所有内容更好int wantedPosition = 25; // Whatever position you're looking for
int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;
if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) {
Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
return;
}
View wantedView = linearLayoutManager.getChildAt(wantedChild);
mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over);
mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup);
mlayoutOver.setVisibility(View.INVISIBLE);
mlayoutPopup.setVisibility(View.VISIBLE);
对于RecycleView,请使用以下代码:
private void updateSetTopState(int index) {
View v = listview.getChildAt(index -
listview.getFirstVisiblePosition()+listview.getHeaderViewsCount());
if(v == null)
return;
TextView aa = (TextView) v.findViewById(R.id.aa);
aa.setVisibility(View.VISIBLE);
}
我想出了另一个解决方案,类似于 RecyclyerView 的方法 notifyItemChanged(int position)
,只需创建一个像这样的 CustomBaseAdapter 类:
public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter {
private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public void notifyItemChanged(int position) {
mDataSetObservable.notifyItemChanged(position);
}
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isEnabled(int position) {
return true;
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
} {
}
}
public class CustomDataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
public void notifyItemChanged(int position) {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
mObservers.get(position).onChanged();
}
}
}
notifyItemChanged(int position)
,当你想要更新一行时,你可以在任何地方调用该方法(例如从按钮点击或任何其他地方)。然后,你的单个行将立即更新。请注意,保留 HTML 标签。View cell = lvOptim.getChildAt(index - lvOptim.getFirstVisiblePosition());
if(cell!=null){
cell = adapter.getView(index, cell, lvOptim); //public View getView(final int position, View convertView, ViewGroup parent)
cell.startAnimation(animationLeftIn());
}
它能够正常工作,但我不确定这是否是一个好的做法。 因此,我不需要实现更新列表项两次的代码。
/**
* A View can only be used (visible) once. This class creates a map from int (position) to view, where the mapping
* is one-to-one and on.
*
*/
private static class UniqueValueSparseArray extends SparseArray<View> {
private final HashMap<View,Integer> m_valueToKey = new HashMap<View,Integer>();
@Override
public void put(int key, View value) {
final Integer previousKey = m_valueToKey.put(value,key);
if(null != previousKey) {
remove(previousKey);//re-mapping
}
super.put(key, value);
}
}
@Override
public void setData(final List<? extends DBObject> data) {
// TODO Implement 'smarter' logic, for replacing just part of the data?
if (data == m_data) return;
List<? extends DBObject> oldData = m_data;
m_data = null == data ? Collections.EMPTY_LIST : data;
if (!updateExistingViews(oldData, data)) notifyDataSetChanged();
else if (DEBUG) Log.d(TAG, "Updated without notifyDataSetChanged");
}
/**
* See if we can update the data within existing layout, without re-drawing the list.
* @param oldData
* @param newData
* @return
*/
private boolean updateExistingViews(List<? extends DBObject> oldData, List<? extends DBObject> newData) {
/**
* Iterate over new data, compare to old. If IDs out of sync, stop and return false. Else - update visible
* items.
*/
final int oldDataSize = oldData.size();
if (oldDataSize != newData.size()) return false;
DBObject newObj;
int nVisibleViews = m_visibleViews.size();
if(nVisibleViews == 0) return false;
for (int position = 0; nVisibleViews > 0 && position < oldDataSize; position++) {
newObj = newData.get(position);
if (oldData.get(position).getId() != newObj.getId()) return false;
// iterate over visible objects and see if this ID is there.
final View view = m_visibleViews.get(position);
if (null != view) {
// this position has a visible view, let's update it!
bindView(position, view, false);
nVisibleViews--;
}
}
return true;
}
当然:
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final View result = createViewFromResource(position, convertView, parent);
m_visibleViews.put(position, result);
return result;
}
忽略bindView的最后一个参数(我用它来确定是否需要回收ImageDrawable的位图)。
如上所述,“可见”视图的总数大致上是屏幕上适合的数量(忽略方向变化等),因此在内存方面没有什么大问题。