我通过ContentProvider和SimpleCursorAdapter加载ListView。我添加了一个TextWatcher来过滤结果。这工作正常,但是当我点击ListItem加载另一个片段后,当我回击时,我得到一个空的ListView。任何想法为什么会发生这种情况?
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.text.Editable;
import android.text.Html;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FilterQueryProvider;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;
import com.actionbarsherlock.app.SherlockListFragment;
import com.brndwgn.RequestHelper.GetResponse;
import com.brndwgn.database.BlogContentProvider;
import com.brndwgn.database.BlogTable;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
public class NewsFragment extends SherlockListFragment implements LoaderCallbacks<Cursor> {
public boolean isNetworkAvailable() {
ConnectivityManager connectivityManager = (ConnectivityManager) parent.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null;
}
private SimpleCursorAdapter adapter;
private boolean dataRetrieved;
private SlidingArea parent;
PullToRefreshListView pullToRefreshView;
EditText searchBox;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
parent = (SlidingArea) getActivity();
setHasOptionsMenu(true);
parent.getSupportActionBar().setCustomView(R.layout.actionbar_news_list);
parent.getSupportActionBar().setDisplayShowCustomEnabled(true);
parent.getSupportActionBar().setDisplayShowTitleEnabled(false);
parent.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
final ImageView searchButton = (ImageView) parent.findViewById(R.id.image_search_list);
searchButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
searchBox.setVisibility(View.VISIBLE); }
});
final ImageView refreshButton = (ImageView) parent.findViewById(R.id.image_refresh_list);
refreshButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
getData(getString(R.string.json_get_posts_url), true);
refreshButton.setImageResource(R.drawable.menu_refresh_dark);
fillData();
}
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View V = inflater.inflate(R.layout.fragment_news, null);
pullToRefreshView = (PullToRefreshListView) V.findViewById(R.id.pull_to_refresh_listview);
searchBox = (EditText)V.findViewById(R.id.edittext_search);
return V;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
pullToRefreshView.setOnRefreshListener(new OnRefreshListener<ListView>() {
@Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
// Do work to refresh the list here.
getData(getString(R.string.json_get_posts_url), false);
}
});
}
@Override
public void onResume() {
super.onResume();
fillData();
parent.getSupportActionBar().setCustomView(R.layout.actionbar_news_list);
parent.getSupportActionBar().setDisplayShowCustomEnabled(true);
parent.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
final ImageView searchButton = (ImageView) parent.findViewById(R.id.image_search_list);
searchButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
searchBox.setVisibility(View.VISIBLE);
}
});
final ImageView refreshButton = (ImageView) parent.findViewById(R.id.image_refresh_list);
refreshButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
getData(getString(R.string.json_get_posts_url), true);
refreshButton.setImageResource(R.drawable.menu_refresh_dark);
fillData();
}
});
adapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
String[] projection = new String[] { BlogTable.TITLE_PLAIN, BlogTable.DATE_MODIFIED, BlogTable.EXCERPT, BlogTable.ID };
return parent.getContentResolver().query(BlogContentProvider.CONTENT_URI, projection, BlogTable.TITLE_PLAIN + " LIKE '%"+constraint+"%'", null, BlogTable.DATE_MODIFIED + " DESC");
}
});
searchBox.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {
// When user changed the Text
adapter.getFilter().filter(cs);
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
int arg3) {
// TODO Auto-generated method stub
}
@Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
}
//Constant used as key for ID being passed in the bundle between fragments
public static final String NEWS_ID = "newsID";
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Bundle detailBundle = new Bundle();
detailBundle.putLong(NEWS_ID, id);
FragmentTransaction ft = parent.getSupportFragmentManager().beginTransaction();
//Instance must be created here to setArguments rather then in the replace() method
NewsDetailFragment newsDetailFragment = new NewsDetailFragment();
newsDetailFragment.setArguments(detailBundle);
ft.replace(R.id.content_frame, newsDetailFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(null);
ft.commit();
}
private void getData(String url, boolean showProgressDialog) {
new Request(showProgressDialog).execute(new String[] {url});
}
public class Request extends AsyncTask<String, Void, String> {
ProgressDialog dialog;
/* This is the only file that needs to be edited */
private GetResponse response = null;
private boolean showProgressDialog = true;
public Request(boolean showProgressDialog)
{
super();
this.showProgressDialog = showProgressDialog;
response = new GetResponse();
}
@Override
protected void onPreExecute() {
if (showProgressDialog) {
dialog = new ProgressDialog(parent);
dialog.setMessage("Loading stuff to read...");
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.show();
}
}
//This method must return the type specified in the constructor
@Override
protected String doInBackground(String... url) {
response.setUrl(url[0]);
String res = response.execute();
// When it returns the "res" it will call onPostExecute
return res;
}
@Override
protected void onPostExecute(String result) {
// Here we have response from server
if ( isNetworkAvailable() ){
try {
JSONObject json = new JSONObject(result);
JSONArray arr = json.getJSONArray("posts");
for (int i = arr.length() - 1; i >= 0; --i) {
JSONObject row = arr.getJSONObject(i);
if (row.getString("type").equals("post")) {
if (row.getString("status").equals("publish")) {
ContentValues values = new ContentValues();
values.put(BlogTable.POST_ID, row.getString("id"));
values.put(BlogTable.URL, row.getString(BlogTable.URL));
values.put(BlogTable.TITLE, row.getString(BlogTable.TITLE));
values.put(BlogTable.TITLE_PLAIN, Html.fromHtml(row.getString(BlogTable.TITLE_PLAIN)).toString());
values.put(BlogTable.CONTENT, row.getString(BlogTable.CONTENT));
values.put(BlogTable.EXCERPT, Html.fromHtml(row.getString(BlogTable.EXCERPT)).toString());
values.put(BlogTable.DATE, row.getString(BlogTable.DATE));
values.put(BlogTable.DATE_MODIFIED, row.getString(BlogTable.DATE_MODIFIED));
//getAuthorID
JSONObject objAuthor = row.getJSONObject("author");
values.put(BlogTable.AUTHOR_ID, objAuthor.getInt("id"));
//Check to insert new author
//getThumbnail
byte[] image = AppHelper.getBlobFromURL(row.getString(BlogTable.THUMBNAIL));
if (image != null) {
values.put(BlogTable.DATE_MODIFIED, image);
}
//isFavourite
values.put(BlogTable.IS_FAVOURITE, 0);
// when this is called here it is referring to the activity
parent.getContentResolver().insert(BlogContentProvider.CONTENT_URI, values);
dataRetrieved = true;
}
else {
String currentID = row.getString("id");
// Delete unpublished fields
parent.getContentResolver().delete(BlogContentProvider.CONTENT_URI, "_id = " + currentID, null);
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
else {
Toast toast = Toast.makeText( parent , "Oops! There is no connection to the Internet", Toast.LENGTH_SHORT);
toast.show();
}
// Call onRefreshComplete when the list has been refreshed.
pullToRefreshView.onRefreshComplete();
super.onPostExecute(result);
ImageView refreshButton = (ImageView) parent.findViewById(R.id.image_refresh_list);
refreshButton.setImageResource(R.drawable.menu_refresh);
if (showProgressDialog) {
dialog.dismiss();
}
}
}
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
String[] projection = new String[] { BlogTable.TITLE_PLAIN, BlogTable.DATE_MODIFIED, BlogTable.EXCERPT, BlogTable.ID };
CursorLoader loader = new CursorLoader(parent, BlogContentProvider.CONTENT_URI, projection, null, null, BlogTable.DATE_MODIFIED + " DESC");
return loader;
}
private void fillData(){
//_id is expected from this method that is why we used it earlier
String[] from = new String[] { BlogTable.TITLE_PLAIN, BlogTable.DATE_MODIFIED, BlogTable.EXCERPT};
int[] to = new int[] { R.id.text_news_title, R.id.text_news_date, R.id.text_news_excerpt};
//initialize loader to call this class with a callback
getLoaderManager().initLoader(0, null, this);
//We create adapter to fill list with data, but we don't provide any data as it will be done by loader
adapter = new SimpleCursorAdapter(parent, R.layout.news_list_view, null, from, to, 0);
setListAdapter(adapter);
}
public void onLoadFinished(Loader<Cursor> arg0, Cursor data) {
adapter.swapCursor(data);
}
public void onLoaderReset(Loader<Cursor> arg0) {
adapter.swapCursor(null);
}
}
答案 0 :(得分:1)
您观察空ListView的原因是因为返回的Cursor的位置是 STILL 指向最后一个元素。当您将Cursor swap()
转换为适配器时,适配器会尝试使用while(Cursor.moveToNext())
循环进行迭代。因为该循环总是计算FALSE,所以ListView会给你一个空Cursor的错觉。
在Cursor.getCount()
中打印Cursor.getPosition()
和onLoadFinished()
的值。如果我是正确的,这两个值应该相等。这种索引冲突造成了上述错觉。
加载器将尽可能重用光标。如果您为一组未更改的数据请求Loader,则加载程序是智能的并通过onLoadFinished返回Cursor而不执行任何附加工作,甚至不将Cursor的位置设置为-1。
ANS 手动调用Cursor.moveToPosition(-1)
中的onLoadFinished()
以解决此问题。