问题
My tabLayout is exhibiting this strange behaviour
You can see that by swiping the tabs after clicking a row the content from a tab gets mixed with the content from another. Each row in the listView (the gif only shows one row, of course there'd be more) can be clicked to update a value from the database on which the eye icon is based. So, I want the icon to update at the same time.
In the activity source file I also declared the PagerAdapter for the fragment tabs, the fragment and the ListViewAdapter for each fragment.
The code being:
The activity:
class ListEpisodes extends ListAbstract {
private long seriesId; // Identifier of current Series
private String seriesTitle; // Title of current Series
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a {@link FragmentPagerAdapter}
* derivative, which will keep every loaded fragment in memory.
* If this becomes too memory intensive, it may be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
private SeasonPagerAdapter mSectionsPagerAdapter;
/**
* The {@link ViewPager} that will host the section contents.
*/
private ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_episodes);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
try {
seriesId = (Long) this.getIntent().getExtras().get("sid");
} catch (NullPointerException e) {
seriesId = 0;
}
mDbAdapter = new DbAdapter(this);
mDbAdapter.open();
list();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_list_episodes, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
switch (item.getItemId()) {
case R.id.create_new_episode:
create();
return true;
case R.id.edit_series:
editSeries();
return true;
default:
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onResume() {
super.onResume();
mViewPager.getAdapter().notifyDataSetChanged();
}
/////////////////////////////////////// ListAbstract ///////////////////////////////////////
/**
* Fetches and shows all episodes from the database.
*/
protected void list() {
Cursor series = mDbAdapter.fetchSeries(seriesId);
seriesTitle = series.getString(series.getColumnIndexOrThrow(DbAdapter.SERIES_KEY_TITLE));
getSupportActionBar().setTitle(seriesTitle);
Cursor eCursor = mDbAdapter.getSeasons(seriesId);
ArrayList<Integer> seasons = new ArrayList<>();
eCursor.moveToFirst();
for (int i = 0; i < eCursor.getCount(); i++) {
seasons.add(eCursor.getInt(eCursor.getColumnIndex(DbAdapter.EPISODE_KEY_SEASON_NUM)));
eCursor.moveToNext();
}
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter = new SeasonPagerAdapter(getSupportFragmentManager(),
seasons, seriesId);
mSectionsPagerAdapter.notifyDataSetChanged();
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
}
/**
* Starts an activity to create a new episode.
*/
protected void create() {
Intent i = new Intent(this, EditEpisodes.class);
i.putExtra(DbAdapter.EPISODE_KEY_ID, Long.valueOf(0));
i.putExtra(DbAdapter.EPISODE_KEY_SERIES, seriesId);
startActivityForResult(i, ACTIVITY_CREATE);
}
/**
* Starts an activity to edit an episode.
*
* @param elementId id of the episode that will be edited.
*/
protected void edit(long elementId) {
Intent i = new Intent(this, EditEpisodes.class);
i.putExtra(DbAdapter.EPISODE_KEY_SERIES, seriesId);
i.putExtra(DbAdapter.EPISODE_KEY_ID, elementId);
startActivityForResult(i, ACTIVITY_EDIT);
}
/**
* Deletes the episode elementId.
*
* @param elementId id of the episode that will be deleted.
*/
protected void delete(long elementId) {
// Episodes are refreshed if the current episode has been correctly deleted.
if (mDbAdapter.deleteEpisode(elementId)) {
list();
}
}
/**
* Starts an activity to edit the current series (with identifier [seriesId]).
*/
protected void editSeries() {
Intent i = new Intent(this, EditSeries.class);
i.putExtra(DbAdapter.SERIES_KEY_ID, seriesId);
startActivityForResult(i, ACTIVITY_EDIT);
}
////////////////////////////////////////////////////////////////////////////////////////////
/**
* Fragment containing all episodes that correspond to the same season of a given
* series.
*/
public static class SeasonFragment extends Fragment {
/**
* These arguments can only be passed via bundle. They match to season number and series Id.
*/
private static final String ARG_TAB_NUMBER = "section_number";
private static final String ARG_SERIES_ID = "series_id";
private static final String ARG_SEASONS_ARRAY = "seasons_array";
private ArrayList<Integer> seasons;
private int season = -1;
private int tab = -1;
public SeasonFragment() {
}
/**
* Returns a new instance of this fragment for the given season.
*/
protected static SeasonFragment newInstance(ArrayList<Integer> seasons, int tabNumber,
long seriesId) {
SeasonFragment fragment = new SeasonFragment();
Bundle args = new Bundle();
args.putIntegerArrayList(ARG_SEASONS_ARRAY, seasons);
args.putInt(ARG_TAB_NUMBER, tabNumber);
args.putLong(ARG_SERIES_ID, seriesId);
fragment.setArguments(args);
return fragment;
}
/**
* Fetches and shows all episodes on this fragment
*
* @param inflater to instantiate the season view
* @param container to match the tabs (internal to android)
* @param savedInstanceState argument container, since this class' constructor can't have
* parameters
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
DbAdapter mDbAdapter = new DbAdapter(this.getActivity());
mDbAdapter.open();
tab = getArguments().getInt(ARG_TAB_NUMBER);
long series = getArguments().getLong(ARG_SERIES_ID);
if (tab == 0) {
View rootView = inflater.inflate(R.layout.fragment_description, container, false);
Cursor descriptionCursor = mDbAdapter.fetchSeries(series);
//getActivity().startManagingCursor(descriptionCursor);
String description = descriptionCursor.getString(
descriptionCursor.getColumnIndexOrThrow(DbAdapter.SERIES_KEY_DESCRIPTION));
((TextView) rootView.findViewById(R.id.description)).setText(description);
return rootView;
} else {
seasons = getArguments().getIntegerArrayList(ARG_SEASONS_ARRAY);
assert seasons != null;
season = seasons.get(tab - 1);
View rootView = inflater.inflate(R.layout.fragment_list_episodes, container, false);
// Get seriesId and fetch episodes for the season.
Cursor episodes = mDbAdapter.fetchEpisodesFromSeason(getArguments().getLong(ARG_SERIES_ID),
season);
//getActivity().startManagingCursor(episodes);
EpisodeListViewAdapter adapter = new EpisodeListViewAdapter(this.getContext(), R.layout.episode_row, episodes, 0);
ListView episodeList = (ListView) rootView.findViewById(R.id.episode_list);
episodeList.setAdapter(adapter);
registerForContextMenu(episodeList);
episodeList.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
toggleWatched(id);
}
});
return rootView;
}
}
private void toggleWatched(long episodeId) {
DbAdapter mDbAdapter = new DbAdapter(this.getActivity());
mDbAdapter.open();
mDbAdapter.toggleWatched(episodeId);
// Get seriesId and fetch episodes for the season.
Cursor episodes = mDbAdapter.fetchEpisodesFromSeason(getArguments().getLong(ARG_SERIES_ID),
season);
ListView episodeList = (ListView) this.getActivity().findViewById(R.id.episode_list);
EpisodeListViewAdapter lva = ((EpisodeListViewAdapter) episodeList.getAdapter());
// Sometimes this works sometimes it doesn't
lva.swapCursor(episodes);
lva.notifyDataSetChanged();
episodeList.setAdapter(lva);
}
/**
* Method that creates an options menu when a user clicks and holds on a series.
*/
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.clear();
menu.add(Menu.NONE, EDIT_ID, Menu.NONE, R.string.edit_episode);
menu.add(Menu.NONE, DELETE_ID, Menu.NONE, R.string.delete_episode);
}
/**
* Method called when a ContextMenu option is selected.
*/
@Override
public boolean onContextItemSelected(MenuItem item) {
int i = item.getItemId();
if (i == DELETE_ID) {
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
((ListAbstract) getActivity()).delete(info.id);
return true;
} else if (i == EDIT_ID) {
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
((ListAbstract) getActivity()).edit(info.id);
return true;
}
return super.onContextItemSelected(item);
}
/**
* Adapter class specific to populate the listView in ListSeries from a cursor.
*/
static class EpisodeListViewAdapter extends ResourceCursorAdapter {
public EpisodeListViewAdapter(Context context, int layout, Cursor c, int flags) {
super(context, layout, c, flags);
}
@Override
public void changeCursor(Cursor cursor){
super.changeCursor(cursor);
}
/**
* Will be automatically called by android to populate the list view.
* To do that it extracts the information from the cursor and transforms it to
* something usable in the case of the rating images.
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView numberView = (TextView) view.findViewById(R.id.episode_number);
String episode_number = cursor.getString(cursor.getColumnIndex(DbAdapter.EPISODE_KEY_EPISODE_NUM));
numberView.setText(episode_number);
TextView nameView = (TextView) view.findViewById(R.id.episode_name);
String episode_name = cursor.getString(cursor.getColumnIndex(DbAdapter.EPISODE_KEY_NAME));
if (episode_name.length() > 17) {
try{
int cut = episode_name.indexOf(" ", 5);
episode_name = episode_name.substring(0, cut) + "\n" + episode_name.substring(cut + 1);
}catch(Exception e){
episode_name = episode_name.substring(0,10)+"...";
}
}
nameView.setText(episode_name);
ImageView image = (ImageView) view.findViewById(R.id.episode_watched);
String wasWatched = cursor.getString(cursor.getColumnIndexOrThrow(DbAdapter.EPISODE_KEY_WATCHED));
wasWatched = wasWatched == null ? "0" : wasWatched;
int watched_img = 0;
switch (wasWatched) {
case ("0"):
watched_img = R.drawable.unwatched;
break;
case ("1"):
watched_img = R.drawable.watched;
break;
}
image.setImageResource(watched_img);
}
}
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the seasons.
*/
protected class SeasonPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<Integer> seasons; // Holds all the seasons for the series
private long seriesId; // Id of the series
public SeasonPagerAdapter(FragmentManager fm, ArrayList<Integer> seasons, long seriesId) {
super(fm);
this.seasons = seasons;
this.seriesId = seriesId;
}
@Override
public Fragment getItem(int seasonNum) {
// getItem is called to instantiate the fragment for the given page.
// Return a PlaceholderFragment (defined as a static inner class below).
return SeasonFragment.newInstance(seasons, seasonNum, seriesId);
}
/**
* Forces all fragments to reload on update
*/
@Override
public int getItemPosition(Object object) {
// There are more efficient implementations.
return POSITION_NONE;
}
/**
* @return amount of tabs in the view.
*/
public int getCount() {
// Since the description is in the first tab the count is one more
// than the number of seasons
return seasons.size() + 1;
}
@Override
public CharSequence getPageTitle(int position) {
// The first tab holds the description
if (position == 0) return "Description";
else {
// The title contains the number of the season for the tab
int season = seasons.get(position - 1);
if (season >= 10) return "S" + season;
else return "S0" + season;
}
}
}
}
The activity's layouts:
<LinearLayout
android:id="@+id/test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/AppTheme.PopupOverlay">
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:tabMode="scrollable"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</LinearLayout>
(Content layout)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".activities.ListEpisodes"
tools:showIn="@layout/activity_list_episodes">
<LinearLayout
android:orientation="vertical"
android:id="@+id/linear_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text=""
android:id="@+id/series_title" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text=""
android:id="@+id/series_description" />
</LinearLayout>
</RelativeLayout>
A single row's layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/EDot"
android:textSize="25sp" />
<TextView
android:id="@+id/episode_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/space"
android:textSize="25sp" />
<TextView
android:id="@+id/episode_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp" />
</LinearLayout>
<ImageView
android:id="@+id/episode_watched"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true" />
</RelativeLayout>
回答1:
Ok, the problem in the toggleWatched method. When retrieving the ListView it was going back to the activity to get it. Instead I needed to save in a private variable the ListView object in which the row is and remove this line in the method.
private void toggleWatched(long episodeId) {
...
ListView episodeList = (ListView) this.getActivity().findViewById(R.id.episode_list);
...
}
来源:https://stackoverflow.com/questions/37315602/weird-tab-behavior-whats-wrong-with-the-adapter