如何使用Android ViewPager和FragmentStatePagerAdapter按日期正确实现无限滚动?

时间:2018-04-05 21:02:08

标签: java android android-fragments android-viewpager fragmentstatepageradapter

我正在开发一个简单的Android应用程序(待办事项列表),并且我一直试图使用ViewPager来实现FragmentStatePagerAdapter来导航任务。为简单起见,我在当天,前一天和后一天添加了一些任务(不处理没有任务的日子,只是为了保持简单)。

我无法在滚动屏幕时加载正确的日期。当我第一次向左滑动到前一天时,我得到了正确的结果 - 显示前一天。但是后来我不能向左滑动以获得更多的日子(我认为我的代码将设置前一个"左边"日期为当前"中间"日期并让我滑动再次离开)。在尝试滚动到最右边的日期然后再向左滚动之后,我最终得到NullPointerException,但我无法弄清楚如何更改代码以正确刷新日期。滑动序列(左右 - 右 - 左)后的堆栈跟踪:

04-05 22:38:24.966 12441-12441/ptmprojects.com.quicktickcalendar2 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: ptmprojects.com.quicktickcalendar2, PID: 12441
    java.lang.NullPointerException: Attempt to invoke virtual method 'android.support.v4.app.FragmentTransaction android.support.v4.app.FragmentTransaction.add(int, android.support.v4.app.Fragment)' on a null object reference
        at android.support.v4.app.FragmentStatePagerAdapter.instantiateItem(FragmentStatePagerAdapter.java:123)
        at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:1004)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1186)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1086)
        at android.support.v4.view.ViewPager$3.run(ViewPager.java:267)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:933)
        at android.view.Choreographer.doCallbacks(Choreographer.java:742)
        at android.view.Choreographer.doFrame(Choreographer.java:671)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:919)
        at android.os.Handler.handleCallback(Handler.java:761)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at android.os.Looper.loop(Looper.java:156)
        at android.app.ActivityThread.main(ActivityThread.java:6605)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:999)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:889)

我创建了一个TasksBank,其中所有任务都存储在List<SingleTask> mAllTasksList中,以便当屏幕上显示正确的日期时,从该银行获取任务以将其加载到ViewPager。正如我在Activity点击Button时在其他ActionBar中看到的那样,每天都会正确加载任务。这是一个屏幕截图,用于确认任务是从TasksBank正确加载的不同日期:

All tasks loaded properly

任务由班级SimpleTask表示,每个mDate都有一个字段SimpleTask,用于存储分配给每项任务的日期(我知道Map<LocalDate, SimpleTask>可能会更好选择,但为了简单起见,我选择从ArrayList<SimpleTask>开始 - 如果这是一个非常糟糕的主意,请纠正我。)

为了让您更好地了解它的外观,下面是SingleDayFragment的外观截图(它是SingleTask中存储的RecyclerView列表:

SingleFragment storing tasks for one day

我想解决这个问题的方法是在List<LocalDate> mListOfDates中创建一个DailyTaskPagerActivity来存储3个日期 - 任务的日期显示在左侧滑动,日期显示在中间屏幕上,日期显示在显示在右侧滑动。滚动到另一天之后,我应该将前一个左或右片段设置为当前中间片段,并在需要时将另一天添加到天数列表中。我还创建了字段:currentPositioncurrentDate来存储当前显示的屏幕的详细信息。

我包含我认为与我面临的问题相关的.java个文件,但我认为第一个 - DailyTaskPagerActivity.java是我遇到问题的文件。如果它在这种情况下太多或不够,请告诉我 - 我通过适当的编辑包含所需的一切或删除不应该存在的内容。

DailyTaskPagerActivity.java(负责在几天之间滚动的主要班级 - 我很确定这里有问题)。我对在评论中包含每个代码元素的目的做了一些解释:

public class DailyTaskPagerActivity extends AppCompatActivity {

    private ViewPager mViewPager;
    private List<LocalDate> mListsOfDates;
    private TasksDaysStatePagerAdapter mAdapter;
    int currentPosition;
    LocalDate currentDate;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_daily_task_pager);

        mViewPager = (ViewPager) findViewById(R.id.daily_task_view_pager);
        mListsOfDates = new ArrayList<>();
        mListsOfDates.add(new LocalDate().minusDays(1));
        mListsOfDates.add(new LocalDate());
        mListsOfDates.add(new LocalDate().plusDays(1));
        currentPosition = 1; // current position on middle screen / today's date
        currentDate = new LocalDate();

        mAdapter = new TasksDaysStatePagerAdapter(getSupportFragmentManager());
        mViewPager.setAdapter(mAdapter);
        mViewPager.setCurrentItem(1); // to start mViewPager from the middle date (to allow swipe to the left and
                                      // to the right (not only to right if it were 0
    }

    private class TasksDaysStatePagerAdapter extends FragmentStatePagerAdapter {

        public TasksDaysStatePagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            if (position > currentPosition) { // if position after swipe is higher than currentPosition(equal to 1)
                // it means that the screen has been swiped to the right
                currentPosition = position; // update current position field
                currentDate = currentDate.plusDays(1); // update currentDate field
                mListsOfDates.set(2, currentDate); // set updated date to mListsOfDates last element
                notifyDataSetChanged(); // notify that change to mListsOfDates occured
                return SingleDayFragment.newInstance(currentDate); // create new Fragment with updated date
            } else if (position < currentPosition) {// if position after swipe is lower than currentPosition(equal to 1)
                // it means that the screen has been swiped to the left
                currentPosition = position;
                currentDate = currentDate.minusDays(1);
                mListsOfDates.set(0, currentDate);
                notifyDataSetChanged();
                return SingleDayFragment.newInstance(currentDate);
            } else { // I'm not sure if I should include anything here ??
                // currentPosition = position;
                // mViewPager.setAdapter(new TasksDaysStatePagerAdapter(getSupportFragmentManager()));
                return SingleDayFragment.newInstance(currentDate);
            }
        }

        @Override
        public int getCount() {
            return mListsOfDates.size();
        }
    }
}

SingleDayFragment.java(片段为一天,包含TextView日期和RecyclerView,其中包含单日任务)

public class SingleDayFragment extends Fragment {
    private static final String ARG_DATE = "ptmprojects.com.quicktickcalendar.argDate";
    private RecyclerView mTaskRecyclerView;
    private TaskAdapter mAdapter;
    private TextView mDateAboveRecyclerView;
    DateTimeFormatter mDateTimeFormatter = DateTimeFormat.forPattern("dd MMMM yyyy");
    private LocalDate date;

    public static SingleDayFragment newInstance(LocalDate date) {
        Bundle args = new Bundle();
        args.putSerializable(ARG_DATE, date);
        SingleDayFragment fragment = new SingleDayFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        date = getArguments().getSerializable(ARG_DATE) != null ? (LocalDate) getArguments().getSerializable(ARG_DATE) : new LocalDate();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.activity_single_day, container, false);
        mTaskRecyclerView = (RecyclerView) view.findViewById(R.id.single_day_recycler_view);
        mTaskRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        mDateAboveRecyclerView = view.findViewById(R.id.date_above_recycler_view);
        mDateAboveRecyclerView.setText(getString(R.string.date_above_recycler_view, date.toString(mDateTimeFormatter)));

        updateUI();
        return view;
    }

    private void updateUI() { // updating RecyclerView containing tasks for one day
        TasksBank tasksBank = TasksBank.get(getActivity());
        List<SingleTask> tasksForSingleDay = tasksBank.getTasksForDate(date);

        if (mAdapter == null) {
            mAdapter = new TaskAdapter(tasksForSingleDay);
            mTaskRecyclerView.setAdapter(mAdapter);
        } else {
            mAdapter.setTasks(tasksForSingleDay);
            mAdapter.notifyDataSetChanged();
        }

    }

    // private class TaskAdapter...
    // private class TaskHolder...
}

AndroidManifest.xml(删除了一些我认为不需要的行):

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ptmprojects.com.quicktickcalendar">

    <application
        android:allowBackup="true"
        <activity android:name=".DailyTaskPagerActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".SingleDayActivity">
        </activity>
    </application>

</manifest>

TasksBank.java(提供每天任务列表的课程):

public class TasksBank {

    private Context mContext;
    private static TasksBank sTasksBank;
    private List<SingleTask> mAllTasksList;

    public static TasksBank get(Context context) {
        if (sTasksBank == null) {
            sTasksBank = new TasksBank(context);
        }
        return sTasksBank;
    }

    private TasksBank(Context context) {
        mAllTasksList = new ArrayList<>();
        // Just a sample, to check if code is working
        mContext = context.getApplicationContext();
        mAllTasksList.add(new SingleTask(new LocalDate().minusDays(2), "Title2DaysBefore", "Description2DaysBefore", false));
        mAllTasksList.add(new SingleTask(new LocalDate().minusDays(1), "Title1DayBefore", "Description1DayBefore", false));
        mAllTasksList.add(new SingleTask(new LocalDate(), "Title2", "Description2", false));
        mAllTasksList.add(new SingleTask(new LocalDate().plusDays(1), "Title1DayAfter", "Description1DayAfter", false));
        mAllTasksList.add(new SingleTask(new LocalDate().plusDays(2), "Title2DaysAfter", "Description2DaysAfter", false));
        mAllTasksList.add(new SingleTask(new LocalDate(2018, 03, 31), "Title3", "Description3", false));
    }

    public ArrayList<SingleTask> getTasksForDate(LocalDate date) {
        ArrayList<SingleTask> tasksForDate = new ArrayList<>();
        for(SingleTask task : getAllTasksList()) {
            if (task.getDate().equals(date)) {
                tasksForDate.add(task);
            }
        }
        return tasksForDate;
    }

    public List<SingleTask> getAllTasksList() {
        return mAllTasksList;
    }

    public void addTask(SingleTask task) {
        mAllTasksList.add(task);
    }
}

SingleTask.java(代表任务的类,有更多字段,getter和setter,但我不会包含它,因为我认为它无关紧要):

public class SingleTask {
    private String mTitle;
    private LocalDate mDate;

    public SingleTask(LocalDate date, String title) {
        mDate = date;
        mTitle = title;
    }

    public LocalDate getDate() {
        return mDate;
    }

    public void setDate(LocalDate date) {
        mDate = date;
    }

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String title) {
        mTitle = title;
    }
}

只是为了让你知道我认为我进行了大量的研究,我在Stack Overflow中包含了一些我在这里找到的问题的链接,除了看看Stack Overflow之外:

  1. “infinite” scrolling viewpager with days incrementing/decrementing

  2. dynamically add and remove view to viewpager

  3. Update ViewPager dynamically?

  4. Removing fragments from FragmentStatePagerAdapter

  5. 在许多教程中,我已经读过,只需要实现ViewPagergetCount()方法,getItem()正常工作是微不足道的。我怎样才能让它发挥作用,或者任何人都可以理解为什么它现在不起作用?

0 个答案:

没有答案