Android: обновление ListFragment с использованием notifyDataSetChanged() с пользовательским адаптером не работает

Я использую ListFragment с настраиваемым адаптером списка и хочу обновить список, щелкнув значок на панели действий. К сожалению, это не работает, и я понятия не имею, почему.

Мой ItemListFragment:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Log.d("debug","Hallo in ItemListFragment");

    //show ActionBar
    setHasOptionsMenu(true);

    //get reference to activity
    myApp = getActivity().getApplication();

    //check if intent from ItemListActivity is null
    Bundle be = getActivity().getIntent().getExtras();
    if (be == null){
        //if null read local feed
        feed = ReadFeed(fileName);
        Log.d("debug", "Lese Feed lokal :"+feed);
    }else{
        //else get extras from the intent
        feed = (RSSFeed) getActivity().getIntent().getExtras().get("feed");
        Log.d("debug", "Intent von ItemListActivity an ItemListFragment vorhanden");
    }

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
        Bundle savedInstanceState) {
    //inflate the fragment with the custom detail fragment
    View view = inflater.inflate(R.layout.feed_list, null);
    return view;
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    //get listview from layout
    lv = getListView();
    lv.setVerticalFadingEdgeEnabled(true);

    // Set custom list adapter to the ListView        
    adapter = new CustomListAdapter(getActivity(), feed);
    lv.setAdapter(adapter);

}

//Inflate ActionBar
@Override
public void onCreateOptionsMenu(Menu optionsMenu, MenuInflater inflater) {
    inflater.inflate(R.menu.main, optionsMenu);
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    // Restore the previously serialized activated item position.
    if (savedInstanceState != null
            && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
        setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
    }
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);

    // Activities containing this fragment must implement its callbacks.
    if (!(activity instanceof Callbacks)) {
        throw new IllegalStateException("Activity must implement fragment's callbacks.");
    }

    mCallbacks = (Callbacks) activity;
}

@Override
public void onDetach() {
    super.onDetach();

    // Reset the active callbacks interface to the dummy implementation.
    mCallbacks = sCallbacks;
}

@Override
public void onListItemClick(ListView listView, View view, int position, long id) {
    super.onListItemClick(listView, view, position, id);

    // Notify the active callbacks interface (the activity, if the
    // fragment is attached to one) that an item has been selected.
    if (mCallbacks != null) {
        Log.d("debug","Callback in ItemListFragment mit Position: "+position+"und Feed: "+feed);
        mCallbacks.onItemSelected(position, feed);
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    if (mActivatedPosition != ListView.INVALID_POSITION) {
        // Serialize and persist the activated item position.
        outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
    }
}

/**
 * Turns on activate-on-click mode. When this mode is on, list items will be
 * given the 'activated' state when touched.
 */
public void setActivateOnItemClick(boolean activateOnItemClick) {
    // When setting CHOICE_MODE_SINGLE, ListView will automatically
    // give items the 'activated' state when touched.
    getListView().setChoiceMode(activateOnItemClick
            ? ListView.CHOICE_MODE_SINGLE
                    : ListView.CHOICE_MODE_NONE);
}

private void setActivatedPosition(int position) {
    if (position == ListView.INVALID_POSITION) {
        getListView().setItemChecked(mActivatedPosition, false);
    } else {
        getListView().setItemChecked(position, true);
    }

    mActivatedPosition = position;
}

//OnClick auf ActionBar
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case android.R.id.home:
        return true;
    case R.id.refresh_option:
        refreshList(item);
        return true;
    }
    return super.onOptionsItemSelected(item);
}

//Click on refresh in ActionBar -> Refresh the List
public void refreshList(final MenuItem item) {
    /* Attach a rotating ImageView to the refresh item as an ActionView */
    LayoutInflater inflater = (LayoutInflater) getActivity().getApplication()
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    ImageView iv = (ImageView) inflater.inflate(R.layout.action_refresh,
            null);

    Animation rotation = AnimationUtils.loadAnimation(getActivity(),
            R.anim.refresh_rotate);
    rotation.setRepeatCount(Animation.INFINITE);
    iv.startAnimation(rotation);

    item.setActionView(iv);

    // trigger feed refresh:
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            DOMParser tmpDOMParser = new DOMParser();
            feed = tmpDOMParser.parseXml("http://www.example.de/feed");

            Log.d("debug", "Refresh Liste mit Feed: "+feed);

            ItemListFragment.this.getActivity().runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    if (feed != null && feed.getItemCount() > 0) {
                        Log.d("debug", "Aktualisiere Liste");
                        adapter.notifyDataSetChanged();
                        item.getActionView().clearAnimation();
                        item.setActionView(null);
                    }
                }
            });
        }
    });
    thread.start();
}

@Override
public void onDestroy() {
    super.onDestroy();
    //TODO Datenverbrauch dadurch geringer?
    //adapter.imageLoader.clearCache();
    adapter.notifyDataSetChanged();
}   

Мой CustomListAdapter.java

public class CustomListAdapter extends BaseAdapter  {

    private LayoutInflater layoutInflater;
    public ImageLoader imageLoader;
    public RSSFeed _feed;

    public CustomListAdapter(Activity activity, RSSFeed feed) {

        _feed = feed;

        layoutInflater = (LayoutInflater) activity
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        imageLoader = new ImageLoader(activity.getApplicationContext());
    }

    @Override
    public int getCount() {
        // Set the total list item count
        return _feed.getItemCount();
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        // Inflate the item layout and set the views
        View listItem = convertView;
        int pos = position;
        if (listItem == null) {
            listItem = layoutInflater.inflate(R.layout.list_item, null);
        }

        // Initialize the views in the layout
        ImageView iv = (ImageView) listItem.findViewById(R.id.thumb);
        TextView tvTitle = (TextView) listItem.findViewById(R.id.title);
        TextView tvDate = (TextView) listItem.findViewById(R.id.date);
        TextView tvDesc = (TextView) listItem.findViewById(R.id.description);

        // Set the views in the layout
        imageLoader.DisplayImage(_feed.getItem(pos).getImage(), iv);
        tvTitle.setText(_feed.getItem(pos).getTitle());
        tvDate.setText(_feed.getItem(pos).getDate());
        tvDesc.setText(_feed.getItem(pos).getShortDescription());

        return listItem;
    }
}

Это та часть, где я пытаюсь обновить список:

        @Override
        public void run() {
            if (feed != null && feed.getItemCount() > 0) {
                Log.d("debug", "Aktualisiere Liste");
                adapter.notifyDataSetChanged();
                item.getActionView().clearAnimation();
                item.setActionView(null);
            }
        }

Где моя ошибка?


person Mokkapps    schedule 15.11.2013    source источник
comment
Не думаю, что это связано, но вам не нужно делать lv.setAdapter(adapter); при обновлении фида. Кроме того, вы проверили, выполняется ли этот блок кода до конца?   -  person Kai    schedule 16.11.2013
comment
Моя анимация в ActionBar останавливается, поэтому я думаю, что код работает полностью   -  person Mokkapps    schedule 16.11.2013
comment
В этом потоке вы снова анализируете xml и назначаете его переменной feed, а затем вызываете notifyDataSetChanged(), но это не будет обновлять адаптер, потому что вы не обновляете фактическую ссылку на данные, хранящиеся в адаптере (поэтому он по-прежнему будет видеть старую список данных). Поэтому, когда вам нужно обновить адаптер, перед вызовом notifyDataSetChanged() сначала обновите переменную _feed адаптера, чтобы она указывала на новый проанализированный feed (вы можете добавить в адаптер метод установки, чтобы назначить feed на _feed (и вызвать его в потоке обновления).   -  person user    schedule 18.11.2013
comment
Вы спасли мой день, который работает! Не могли бы вы написать новый комментарий, чтобы я мог отметить его как решение!   -  person Mokkapps    schedule 19.11.2013


Ответы (2)


Ваш текущий код обновления адаптера не будет работать, потому что вы не обновляете правильную ссылку на данные, на которой действительно основан адаптер. Когда вы вызываете refreshList, вы создаете этот поток для анализа xml и присвоения результатов переменной feed, однако ваш адаптер имеет собственную ссылку на исходные данные (_feed), на которые не влияет предыдущее назначение, поэтому в момент notifyDataSetChanged() он все равно увидит старые данные и ничего не сделает.

Решение состоит в том, чтобы обновить ссылку _feed адаптера, чтобы она указывала на новый набор проанализированных результатов, а затем вызвать notifyDataSetChanged() на адаптере.

person user    schedule 19.11.2013

        Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        DOMParser tmpDOMParser = new DOMParser();
        feed = tmpDOMParser.parseXml("http://www.example.de/feed");

        Log.d("debug", "Refresh Liste mit Feed: "+feed);

        ItemListFragment.this.getActivity().runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (feed != null && feed.getItemCount() > 0) {
                    Log.d("debug", "Aktualisiere Liste");
    // Set custom list adapter to the ListView        
  adapter = new CustomListAdapter(getActivity(), feed);
      lv.setAdapter(adapter);
                     adapter.notifyDataSetChanged();
                    item.getActionView().clearAnimation();
                    item.setActionView(null);
                }
            }
        });
    }
});
thread.start();

а также вместо кнопки обновления вы можете использовать this.this поможет вам. pullorefresh

person Invader    schedule 21.11.2013
comment
Это сработает. Без сомнения, но каждый раз, когда вы доберетесь до конца, вы попадете в верхнюю часть списка. Это приведет к очень плохому взаимодействию с пользователем... Следовательно, это не решение. - person Bala Vishnu; 17.05.2014
comment
Какая польза от notifyDataSetChanged Если вы делаете setAdapter снова и снова. - person Ajay Shrestha; 24.01.2016