调用 notifyDataSetChanged 时未更新带有 customAdapter 的 ListView?
Posted
技术标签:
【中文标题】调用 notifyDataSetChanged 时未更新带有 customAdapter 的 ListView?【英文标题】:ListView with customAdapter not updated when notifyDataSetChanged is called? 【发布时间】:2016-01-20 17:52:38 【问题描述】:我有一个带有两个适配器的 Listview。他们收到一个包含一些数据的数组,当滚动到列表视图的底部时,更多数据被加载到数组中,这里列表视图应该添加新数据。
问题:
如何使列表视图反映新数据,为什么在我调用 adAdapter.notifyDataSetChanged();
时它没有更新?
我在 onPostExecute 中下载数据和更新 UI 的片段内部类:
@Override
protected void onPostExecute(String result)
super.onPostExecute(result);
numberofpagesshown = numberofpagesshown + 1;
if(numberofpagesshown == 1 )
list = (ListView) getActivity().findViewById(R.id.search_listview_movie);
adapter = new SearchListViewAdapter(getActivity(), mylist);
adAdapter = new BannerAdListView2(getActivity(), adapter);
// list.setAdapter(adapter); this works when adapter.notifyDataSetChanged(); also is in the else statement but here I don't use the BannerAdListView2 adapter which i want to?
list.setAdapter(adAdapter);
bar.setVisibility(View.GONE);
else
// adapter.notifyDataSetChanged(); // Here the listview should get refresh with the new data
adAdapter.notifyDataSetChanged();
bar.setVisibility(View.GONE);
// Attach the listener to the AdapterView onCreate
list.setOnScrollListener(new EndlessScrollListener()
@Override
public void onLoadMore(int page, int totalItemsCount)
// Triggered only when new data needs to be appended to the list
// Append new items to AdapterView
if(pageNumberInt < totalPagesInt)
++incre;
new DownloadJSON().execute();
);
这是我的 SearchListViewAdapter:
public class SearchListViewAdapter extends BaseAdapter
Context context;
ArrayList<HashMap<String, String>> data;
HashMap<String, String> mylist = new HashMap<>();
static String url;
static String title;
public SearchListViewAdapter(Context a, ArrayList<HashMap<String, String>> d)
context = a;
data = d;
public int getCount()
return data.size();
public HashMap<String, String> getItem(int position)
return data.get(position);
public long getItemId(int position)
return position;
public View getView(final int position, View convertView, ViewGroup parent)
// Avoid unneccessary calls to findViewById() on each row
final ViewHolder holder;
/*
* If convertView is not null, reuse it directly, no inflation
* Only inflate a new View when the convertView is null.
*/
if (convertView == null)
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.search_list_item, parent, false);
holder = new ViewHolder();
holder.poster = (ImageView) convertView.findViewById(R.id.list_image);
holder.textForTitle = (TextView) convertView.findViewById(R.id.search_title);
holder.textForTitle.setTag(position);
convertView.setTag(holder);
else
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
holder.textForTitle.setTag(data.get(position));
mylist = data.get(position);
final String mediaType = mylist.get("media_type");
if (mediaType.equals("movie"))
String posterPath = mylist.get("poster_path");
url = "http://image.tmdb.org/t/p/w185" + posterPath;
title = mylist.get("title");
else if (mediaType.equals("tv"))
String posterPath = mylist.get("poster_path");
url = "http://image.tmdb.org/t/p/w185" + posterPath;
title = mylist.get("original_name");
else
String posterPath = mylist.get("profile_path");
url = "http://image.tmdb.org/t/p/w185" + posterPath;
title = mylist.get("name");
// set image url correctly
// sizes for image 45, 92, 154, 185, 300, 500
// load image url into poster
Picasso.with(context).load(url).into(holder.poster);
// set title to textview
holder.textForTitle.setText(title);
return convertView;
class ViewHolder
ImageView poster;
TextView textForTitle;
还有我的 BannerAdListView2 适配器:
public class BannerAdListView2 extends BaseAdapter
Activity activity;
Context context;
BaseAdapter delegate;
int k = 7;
int baseItems;
int noAds;
// Constructor takes in a BaseAdapter
public BannerAdListView2(Activity activity, BaseAdapter delegate )
this.activity = activity;
this.delegate = delegate;
baseItems = delegate.getCount();
noAds = baseItems / k;
LayoutInflater mLayoutInflater = (LayoutInflater) this.activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@Override
public int getCount()
// Total count includes list items and ads.
return baseItems + noAds;
@Override
public Object getItem(int position)
// Return null if an item is an ad. Otherwise return the delegate item.
if (isItemAnAd(position))
return null;
return delegate.getItem(getOffsetPosition(position));
@Override
public long getItemId(int position)
return position;
@Override
public int getViewTypeCount()
return delegate.getViewTypeCount() + noAds;
@Override
public int getItemViewType(int position)
if (isItemAnAd(position))
return delegate.getViewTypeCount();
else
return delegate.getItemViewType(getOffsetPosition(position));
@Override
public boolean areAllItemsEnabled()
return false;
@Override
public boolean isEnabled(int position)
return (!isItemAnAd(position)) && delegate.isEnabled(getOffsetPosition(position));
private boolean isItemAnAd(int position)
if (position < k) return false;
// Calculate current offset caused by ads already embedded
if (position==k)
return true;
else
return isItemAnAd(position-k);
// Get the position that is offset by the insertion of the ads
private int getOffsetPosition(int position)
int currentNoAds = position / k;
return position - currentNoAds;
@Override
public View getView(int position, View convertView, ViewGroup parent)
// Display every n list items
if (isItemAnAd(position))
if (convertView instanceof AdView)
// Don’t instantiate new AdView, reuse old one
return convertView;
else
// Create a new AdView
AdView adView = new AdView(activity);
adView.setAdSize(AdSize.BANNER);
String bannerId = "My unit id";
adView.setAdUnitId(bannerId);
// Disable focus for sub-views of the AdView to avoid problems with
// trackpad navigation of the list.
for (int i = 0; i < adView.getChildCount(); i++)
adView.getChildAt(i).setFocusable(false);
adView.setFocusable(false);
// Convert the default layout parameters so that they play nice with
// ListView.
float density = activity.getResources().getDisplayMetrics().density;
int height = Math.round(AdSize.BANNER.getHeight() * density);
AbsListView.LayoutParams params = new AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT,
height);
adView.setLayoutParams(params);
AdRequest bannerIntermediateReq = new AdRequest.Builder()
.addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
.addTestDevice("d9e108ab") //means that a test ad is shown on my phone
.build();
adView.loadAd(bannerIntermediateReq);
return adView;
else
// Offload displaying other items to the delegate
return delegate.getView(getOffsetPosition(position) ,
convertView, parent);
编辑: 更改 BannerAdListView2 的构造函数
// Constructor takes in a BaseAdapter
public BannerAdListView2(Activity activity, final BaseAdapter delegate )
this.activity = activity;
this.delegate = delegate;
delegate.registerDataSetObserver(new DataSetObserver()
public void onChanged()
baseItems = delegate.getCount();
noAds = baseItems / k;
public void onInvalidated()
notifyDataSetInvalidated();
);
baseItems = delegate.getCount();
noAds = baseItems / k;
LayoutInflater mLayoutInflater = (LayoutInflater) this.activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
在片段中
@Override
protected void onPostExecute(String result)
super.onPostExecute(result);
numberofpagesshown = numberofpagesshown + 1;
if(numberofpagesshown == 1 )
list = (ListView) getActivity().findViewById(R.id.search_listview_movie);
adapter = new SearchListViewAdapter(getActivity(), mylist);
adAdapter = new BannerAdListView2(getActivity(), adapter);
list.setAdapter(adAdapter);
bar.setVisibility(View.GONE);
else
adapter.notifyDataSetChanged();
bar.setVisibility(View.GONE);
【问题讨论】:
呵呵,你只是不放弃...这显然是因为public int getCount() return baseItems + noAds;
...这个实现仍然过于复杂,无法分析它...只需将它画在纸上...
不,我解决了我之前遇到的问题。但这应该包括两者? adview 计数和来自其他适配器的视图?
baseItems 仅在 BannerAdListView2 构造函数中设置 .. 所以即使内部适配器更改了它的项目计数外部适配器也不知道它...
所以每次数据更改时我都应该调用 BannerAdListView2 吗?这似乎不太实用?
no ... 而是在外部适配器中实现 DataSetObserver ... 并将其注册到内部适配器 ... 然后在外部适配器的 onChanged() 中执行与构造函数中相同的操作( baseItems = delegate. getCount(); noAds = baseItems / k;) 并使 self(notifyDataSetChanged - 外部适配器) 无效 ...现在当您更改内部适配器中的数据时,只需在内部适配器上调用 notifyDataSetChanged ...说真的,先在纸上画出来(我会这样做)这不是一件容易的事......
【参考方案1】:
试试这个解决方案...DecorerAdapter:
public static abstract class DecorerAdapter extends BaseAdapter
public int DECORER_ITEM_TYPE;
private final BaseAdapter mInnerAdapter;
private final int mRepeatAfterEvery;
private int mCount;
public DecorerAdapter(BaseAdapter innerAdapter, int repeatAfterEvery)
mInnerAdapter = innerAdapter;
mRepeatAfterEvery = repeatAfterEvery;
mInnerAdapter.registerDataSetObserver(new DataSetObserver()
@Override
public void onChanged()
notifyDataSetChanged();
@Override
public void onInvalidated()
notifyDataSetInvalidated();
);
setupAdapter();
@Override
public void notifyDataSetChanged()
setupAdapter();
super.notifyDataSetChanged();
private void setupAdapter()
mCount = mInnerAdapter.getCount();
mCount += (mCount + mRepeatAfterEvery - 2) / (mRepeatAfterEvery - 1);
DECORER_ITEM_TYPE = mInnerAdapter.getViewTypeCount();
@Override
public int getCount()
return mCount;
@Override
public Object getItem(int position)
if(position % mRepeatAfterEvery == 0)
return null;
return mInnerAdapter.getItem(calculateInnerPosition(position));
private int calculateInnerPosition(int position)
return position - (position + mRepeatAfterEvery - 1) / mRepeatAfterEvery;
@Override
public long getItemId(int position)
if(position % mRepeatAfterEvery == 0)
return -1;
return mInnerAdapter.getItemId(calculateInnerPosition(position));
@Override
public int getItemViewType(int position)
if(position % mRepeatAfterEvery == 0)
return DECORER_ITEM_TYPE;
return mInnerAdapter.getItemViewType(calculateInnerPosition(position));
@Override
public boolean hasStableIds()
return mInnerAdapter.hasStableIds();
@Override
public int getViewTypeCount()
return mInnerAdapter.getViewTypeCount() + 1;
@Override
public View getView(int position, View convertView, ViewGroup parent)
if(position % mRepeatAfterEvery == 0)
return getDecorerView((position + mRepeatAfterEvery - 1) / mRepeatAfterEvery, convertView, parent);
return mInnerAdapter.getView(calculateInnerPosition( position), convertView, parent);
public abstract View getDecorerView(int position, View convertView, ViewGroup parent);
使用代码:
package pl.selvin.decoreradapter;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
public class MainActivity extends AppCompatActivity
/*** paste the DecorerAdapter class here ***/
public static class MyListFragment extends ListFragment
final ArrayList<String> strings = new ArrayList<>();
private BaseAdapter innerAdapter;
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
menu.add("Add rnd(10) more");
super.onCreateOptionsMenu(menu, inflater);
final Random rnd = new Random();
@Override
public boolean onOptionsItemSelected(MenuItem item)
final String[] toAdd = new String[rnd.nextInt(10)];
int start = strings.size();
for(int i = 0; i < toAdd.length; i++)
toAdd[i] = (start + i) + "";
Collections.addAll(strings, toAdd);
Toast.makeText(getActivity(), "Added " + toAdd.length + " count: " + strings.size(), Toast.LENGTH_SHORT).show();
innerAdapter.notifyDataSetChanged();
return true;
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
for(int i = 0; i < 4; i++)
strings.add(i + "");
innerAdapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, strings);
setListAdapter(new DecorerAdapter(innerAdapter, 5)
@Override
public View getDecorerView(int position, View convertView, ViewGroup parent)
if(convertView == null)
convertView = new TextView(getActivity());
convertView.setBackgroundColor(Color.RED);
((TextView)convertView).setText("AdView: " + position);
return convertView;
);
@Override
public void onListItemClick(ListView l, View v, int position, long id)
String item = (String)l.getItemAtPosition(position);
Toast.makeText(getActivity(), "Item click: " + (item == null ? "ADVIEW" : item), Toast.LENGTH_LONG).show();
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
if (savedInstanceState == null)
getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new MyListFragment(), "LIST").commit();
它在传递给构造函数的每个元素上重复装饰器视图 ...
你需要做的就是以与普通适配器中的getView类似的方式实现getDecorerView
【讨论】:
这样流畅多了!您能解释一下为什么将 (mCount + mRepeatAfterEvery - 2) / (mRepeatAfterEvery - 1) 添加到 mCount (在 void setup adapter 中)吗?我不太明白那里的数学? 智能天花板......它只计算 mCount 的装饰器视图的数量,如果你在每个 mRepeatAfterEvery 重复它 ***.com/questions/7139382/… ... (int) Math.ceil((double) mCount / (mRepeatAfterEvery -1))) 同样的故事:return position - (position + mRepeatAfterEvery - 1) / mRepeatAfterEvery;
我们已经添加了多少 decore 视图? (int) Math.ceil((double) mCount / mRepeatAfterEvery))
如您所见,无需递归即可完成
好的,非常感谢。可以将类似的方法用于网格视图吗?或者是否需要使用交错的网格视图(更类似于列表视图)?以上是关于调用 notifyDataSetChanged 时未更新带有 customAdapter 的 ListView?的主要内容,如果未能解决你的问题,请参考以下文章
当调用 RecyclerView 适配器的“notifyDataSetChanged()”时,RecyclerView 抛出“java.lang.Throwable: addInArray”
带有ListView的Android ViewPager在调用notifyDatasetchanged时无法记住最后一个位置
Android 从 AsyncTask 调用 notifyDataSetChanged
如何为自定义 ExpandableListView 调用 notifyDataSetChanged?
每当在 recyclerview 中为 base64 图像调用 notifydatasetchanged() 时,图像就会闪烁