自定义适配器不更新 gridview
Custom adapter not updating gridview
我正在编写一个应用程序以从 MovieDB api 中提取电影数据并在 GridView 中显示信息。
当应用程序首次加载时,我希望视图最初会填充,但事实并非如此。我在菜单栏中有一个排序选项,当第一次设置排序选项时,GridView 按受欢迎程度按电影顺序填充,就像最初应该的那样,但不管实际选择了什么排序标准。
我已使用日志确定从 API 中检索并正确处理了正确的数据,因此我必须假设适配器未正确更新视图。
为什么视图最初没有显示或没有按应有的方式更新?
FilmFragment.java:
public class FilmFragment extends Fragment {
private ArrayList<FilmParcelable> filmParcels = new ArrayList<FilmParcelable>();
private ImageAdaptor mFilmAdaptor;
protected String[] sortOptions = {
"popularity.desc",
"vote_average.desc"
};
protected String sortBy = sortOptions[0];
private final String LOG_TAG = FilmFragment.class.getSimpleName();
public FilmFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if (savedInstanceState == null || !savedInstanceState.containsKey("films")){
updateFilms();
mFilmAdaptor = new ImageAdaptor(getActivity(),filmParcels);
} else {
filmParcels = savedInstanceState.getParcelableArrayList("films");
mFilmAdaptor = new ImageAdaptor(getActivity(),filmParcels);
}
// allow fragment to handle menu events
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.filmfragment, menu);
}
public boolean onOptionsItemSelected(MenuItem item){
//Handle action bar item clicks. The action bar will
//automatically handle clicks on the Home/Up button, so long
//as you specify a parent activity in AndroidManifest.xml
int id = item.getItemId();
if (id == R.id.action_sort){
showSortDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onSaveInstanceState(Bundle outState){
outState.putParcelableArrayList("films", filmParcels);
super.onSaveInstanceState(outState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
// Find GridView to populate with poster images
GridView gridView = (GridView) rootView.findViewById(R.id.gridView);
// Set the adaptor of the GridView to my ImageAdaptor
gridView.setAdapter(mFilmAdaptor);
updateAdaptor();
return rootView;
}
// Update movie data in case there is a change in the "sort by" option
// Or the fragment is started with no saved data
public void updateFilms(){
new FetchFilmTask().execute();
}
public void updateAdaptor(){
mFilmAdaptor.clear();
mFilmAdaptor.addAll(filmParcels);
mFilmAdaptor.notifyDataSetChanged();
}
// Show dialog sort pop up
public void showSortDialog(){
DialogFragment dialog = new SortDialog();
dialog.setTargetFragment(this, 0);
dialog.show(getActivity().getSupportFragmentManager(), "SortDialog");
}
// If a fragment or activity called by this fragment returns to this fragment,
// Get the information returned via the intent
public void onActivityResult(int requestCode, int resultCode, Intent data){
if (requestCode == 0){
int mSelected = data.getIntExtra("Selected Option", -1);
if (mSelected != -1){
sortBy = sortOptions[mSelected];
updateFilms();
updateAdaptor();
}
}
}
// Class to get JSON data from The Movie Database API
public class FetchFilmTask extends AsyncTask<Void, Void, FilmParcelable[]> {
private final String LOG_TAG = FetchFilmTask.class.getSimpleName();
private final String MOVIE_DB_API_KEY = "e1968ef8ba074d7d5bf07a59de8b2310";
protected FilmParcelable[] doInBackground(Void... params){
// These two need to be declared outside the try/catch
// so that they can be closed in the finally block.
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
// Will contain raw JSON response as a string
String movieDBStr = null;
try {
// Construct URL for Movie DB query
Uri.Builder builder = new Uri.Builder();
builder.scheme("http")
.authority("api.themoviedb.org")
.appendPath("3")
.appendPath("discover")
.appendPath("movie")
.appendQueryParameter("api_key", MOVIE_DB_API_KEY)
.appendQueryParameter("sort_by", sortBy);
String myUrl = builder.build().toString();
Log.d(LOG_TAG, myUrl);
URL url = new URL(myUrl);
// Create the request to The Movie DB, and open the connection
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
// Read the input stream into a String
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
if (inputStream == null){
return null;
}
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
// Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
// But it does make debugging a *lot* easier if you print out the completed
// buffer for debugging.
buffer.append(line + "\n");
}
if (buffer.length() == 0) {
return null;
}
movieDBStr = buffer.toString();
} catch (IOException e){
Log.e(LOG_TAG, "Error: ", e);
return null;
} finally {
if (urlConnection != null){
urlConnection.disconnect();
}
if (reader != null){
try{
reader.close();
} catch (final IOException e){
Log.e(LOG_TAG, "Error closing stream", e);
}
}
}
try {
return getFilmDataFromJson(movieDBStr);
} catch (JSONException e){
Log.e(LOG_TAG, e.getMessage(), e);
e.printStackTrace();
}
return null;
}
/**
* Take the String representing the complete forecast in JSON Format and
* pull out the data we need to construct the Strings needed for the wireframes.
*
* Fortunately parsing is easy: constructor takes the JSON string and converts it
* into an Object hierarchy for us.
*/
private FilmParcelable[] getFilmDataFromJson(String movieDBStr)
throws JSONException {
// JSON objects that need to be extracted
final String MDB_RESULTS = "results";
final String MDB_ID = "id";
final String MDB_SYNOPSIS = "overview";
final String MDB_RELEASE = "release_date";
final String MDB_POSTER = "poster_path";
final String MDB_TITLE = "title";
final String MDB_RATING = "vote_average";
JSONObject filmJson = new JSONObject(movieDBStr);
JSONArray filmArray = filmJson.getJSONArray(MDB_RESULTS);
FilmParcelable[] resultFilms = new FilmParcelable[filmArray.length()];
for (int i = 0; i < filmArray.length(); i++){
// Data needed by the FilmParcelable
int id;
String title;
String releaseDate;
String posterUrl;
Double voteAverage;
String plotSynopsis;
JSONObject film = filmArray.getJSONObject(i);
id = film.getInt(MDB_ID);
plotSynopsis = film.getString(MDB_SYNOPSIS);
releaseDate = film.getString(MDB_RELEASE);
posterUrl = "http://image.tmdb.org/t/p/w300" + film.getString(MDB_POSTER);
title = film.getString(MDB_TITLE);
voteAverage = film.getDouble(MDB_RATING);
Log.d(LOG_TAG, title);
Log.d(LOG_TAG, posterUrl);
resultFilms[i] = new FilmParcelable(id, title, releaseDate, posterUrl, voteAverage, plotSynopsis);
}
return resultFilms;
}
@Override
protected void onPostExecute(FilmParcelable[] result){
if (result != null){
filmParcels = new ArrayList<>(Arrays.asList(result));
}
}
}
}
ImageAdaptor.java:
public class ImageAdaptor extends ArrayAdapter<FilmParcelable> {
public ImageAdaptor(Activity context, ArrayList<FilmParcelable> filmParcels){
super(context, 0, filmParcels);
}
public View getView(int position, View convertView, ViewGroup parent){
Context context= getContext();
View gridView;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
String mUrl = getItem(position).getUrl();
if (convertView == null) {
gridView = inflater.inflate(R.layout.gridview_film_layout, parent, false);
// Find the image view from the gridview_film_layout
ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
// Set the image view to contain image located at mUrl
Picasso.with(getContext()).load(mUrl).into(posterView);
} else {
gridView = convertView;
}
return gridView;
}
}
根据您发布的代码,您一遍又一遍地返回 GridView
的同一个单元格。你应该有这两行
ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
Picasso.with(getContext()).load(mUrl).into(posterView);
出if/else后卫:
if (convertView == null) {
// inflate
} else {
// gridView = convertView;
}
ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
Picasso.with(getContext()).load(mUrl).into(posterView);
return gridView;
我试图在填充用于适配器的数组列表后更新适配器。然而,arraylist 已在后台填充和更新,因此代码:
updateFilms();
updateAdaptor();
导致适配器在数据在后台完成加载之前更新。
修复后 Blackbelt 的解决方案是正确的。
我正在编写一个应用程序以从 MovieDB api 中提取电影数据并在 GridView 中显示信息。
当应用程序首次加载时,我希望视图最初会填充,但事实并非如此。我在菜单栏中有一个排序选项,当第一次设置排序选项时,GridView 按受欢迎程度按电影顺序填充,就像最初应该的那样,但不管实际选择了什么排序标准。
我已使用日志确定从 API 中检索并正确处理了正确的数据,因此我必须假设适配器未正确更新视图。
为什么视图最初没有显示或没有按应有的方式更新?
FilmFragment.java:
public class FilmFragment extends Fragment {
private ArrayList<FilmParcelable> filmParcels = new ArrayList<FilmParcelable>();
private ImageAdaptor mFilmAdaptor;
protected String[] sortOptions = {
"popularity.desc",
"vote_average.desc"
};
protected String sortBy = sortOptions[0];
private final String LOG_TAG = FilmFragment.class.getSimpleName();
public FilmFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if (savedInstanceState == null || !savedInstanceState.containsKey("films")){
updateFilms();
mFilmAdaptor = new ImageAdaptor(getActivity(),filmParcels);
} else {
filmParcels = savedInstanceState.getParcelableArrayList("films");
mFilmAdaptor = new ImageAdaptor(getActivity(),filmParcels);
}
// allow fragment to handle menu events
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.filmfragment, menu);
}
public boolean onOptionsItemSelected(MenuItem item){
//Handle action bar item clicks. The action bar will
//automatically handle clicks on the Home/Up button, so long
//as you specify a parent activity in AndroidManifest.xml
int id = item.getItemId();
if (id == R.id.action_sort){
showSortDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onSaveInstanceState(Bundle outState){
outState.putParcelableArrayList("films", filmParcels);
super.onSaveInstanceState(outState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
// Find GridView to populate with poster images
GridView gridView = (GridView) rootView.findViewById(R.id.gridView);
// Set the adaptor of the GridView to my ImageAdaptor
gridView.setAdapter(mFilmAdaptor);
updateAdaptor();
return rootView;
}
// Update movie data in case there is a change in the "sort by" option
// Or the fragment is started with no saved data
public void updateFilms(){
new FetchFilmTask().execute();
}
public void updateAdaptor(){
mFilmAdaptor.clear();
mFilmAdaptor.addAll(filmParcels);
mFilmAdaptor.notifyDataSetChanged();
}
// Show dialog sort pop up
public void showSortDialog(){
DialogFragment dialog = new SortDialog();
dialog.setTargetFragment(this, 0);
dialog.show(getActivity().getSupportFragmentManager(), "SortDialog");
}
// If a fragment or activity called by this fragment returns to this fragment,
// Get the information returned via the intent
public void onActivityResult(int requestCode, int resultCode, Intent data){
if (requestCode == 0){
int mSelected = data.getIntExtra("Selected Option", -1);
if (mSelected != -1){
sortBy = sortOptions[mSelected];
updateFilms();
updateAdaptor();
}
}
}
// Class to get JSON data from The Movie Database API
public class FetchFilmTask extends AsyncTask<Void, Void, FilmParcelable[]> {
private final String LOG_TAG = FetchFilmTask.class.getSimpleName();
private final String MOVIE_DB_API_KEY = "e1968ef8ba074d7d5bf07a59de8b2310";
protected FilmParcelable[] doInBackground(Void... params){
// These two need to be declared outside the try/catch
// so that they can be closed in the finally block.
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
// Will contain raw JSON response as a string
String movieDBStr = null;
try {
// Construct URL for Movie DB query
Uri.Builder builder = new Uri.Builder();
builder.scheme("http")
.authority("api.themoviedb.org")
.appendPath("3")
.appendPath("discover")
.appendPath("movie")
.appendQueryParameter("api_key", MOVIE_DB_API_KEY)
.appendQueryParameter("sort_by", sortBy);
String myUrl = builder.build().toString();
Log.d(LOG_TAG, myUrl);
URL url = new URL(myUrl);
// Create the request to The Movie DB, and open the connection
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
// Read the input stream into a String
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
if (inputStream == null){
return null;
}
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
// Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
// But it does make debugging a *lot* easier if you print out the completed
// buffer for debugging.
buffer.append(line + "\n");
}
if (buffer.length() == 0) {
return null;
}
movieDBStr = buffer.toString();
} catch (IOException e){
Log.e(LOG_TAG, "Error: ", e);
return null;
} finally {
if (urlConnection != null){
urlConnection.disconnect();
}
if (reader != null){
try{
reader.close();
} catch (final IOException e){
Log.e(LOG_TAG, "Error closing stream", e);
}
}
}
try {
return getFilmDataFromJson(movieDBStr);
} catch (JSONException e){
Log.e(LOG_TAG, e.getMessage(), e);
e.printStackTrace();
}
return null;
}
/**
* Take the String representing the complete forecast in JSON Format and
* pull out the data we need to construct the Strings needed for the wireframes.
*
* Fortunately parsing is easy: constructor takes the JSON string and converts it
* into an Object hierarchy for us.
*/
private FilmParcelable[] getFilmDataFromJson(String movieDBStr)
throws JSONException {
// JSON objects that need to be extracted
final String MDB_RESULTS = "results";
final String MDB_ID = "id";
final String MDB_SYNOPSIS = "overview";
final String MDB_RELEASE = "release_date";
final String MDB_POSTER = "poster_path";
final String MDB_TITLE = "title";
final String MDB_RATING = "vote_average";
JSONObject filmJson = new JSONObject(movieDBStr);
JSONArray filmArray = filmJson.getJSONArray(MDB_RESULTS);
FilmParcelable[] resultFilms = new FilmParcelable[filmArray.length()];
for (int i = 0; i < filmArray.length(); i++){
// Data needed by the FilmParcelable
int id;
String title;
String releaseDate;
String posterUrl;
Double voteAverage;
String plotSynopsis;
JSONObject film = filmArray.getJSONObject(i);
id = film.getInt(MDB_ID);
plotSynopsis = film.getString(MDB_SYNOPSIS);
releaseDate = film.getString(MDB_RELEASE);
posterUrl = "http://image.tmdb.org/t/p/w300" + film.getString(MDB_POSTER);
title = film.getString(MDB_TITLE);
voteAverage = film.getDouble(MDB_RATING);
Log.d(LOG_TAG, title);
Log.d(LOG_TAG, posterUrl);
resultFilms[i] = new FilmParcelable(id, title, releaseDate, posterUrl, voteAverage, plotSynopsis);
}
return resultFilms;
}
@Override
protected void onPostExecute(FilmParcelable[] result){
if (result != null){
filmParcels = new ArrayList<>(Arrays.asList(result));
}
}
}
}
ImageAdaptor.java:
public class ImageAdaptor extends ArrayAdapter<FilmParcelable> {
public ImageAdaptor(Activity context, ArrayList<FilmParcelable> filmParcels){
super(context, 0, filmParcels);
}
public View getView(int position, View convertView, ViewGroup parent){
Context context= getContext();
View gridView;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
String mUrl = getItem(position).getUrl();
if (convertView == null) {
gridView = inflater.inflate(R.layout.gridview_film_layout, parent, false);
// Find the image view from the gridview_film_layout
ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
// Set the image view to contain image located at mUrl
Picasso.with(getContext()).load(mUrl).into(posterView);
} else {
gridView = convertView;
}
return gridView;
}
}
根据您发布的代码,您一遍又一遍地返回 GridView
的同一个单元格。你应该有这两行
ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
Picasso.with(getContext()).load(mUrl).into(posterView);
出if/else后卫:
if (convertView == null) {
// inflate
} else {
// gridView = convertView;
}
ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
Picasso.with(getContext()).load(mUrl).into(posterView);
return gridView;
我试图在填充用于适配器的数组列表后更新适配器。然而,arraylist 已在后台填充和更新,因此代码:
updateFilms();
updateAdaptor();
导致适配器在数据在后台完成加载之前更新。
修复后 Blackbelt 的解决方案是正确的。