Weird tab behavior, what's wrong with the adapter

天涯浪子 提交于 2019-12-12 04:29:26

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!