renaud.io

Yet Another Command Line Experiments.

ViewPager & the verticality of Fragments

Permalink

Back to august 2010, Rich Hyndman posts an entry on the android developers’ blog entitled “Horizontal View Swiping with ViewPager”. I already was really enthusiastic about the Android Support Package at that time because of the introduction of the LoaderManager which save us big headaches on the proper cursor management (but that’s another story). In an application of mine, I’ve used an onFling/swipe gesture based on some 2009 codes found on the Web (codeshogun.com and/or ceveni.com for example).

The main drawbacks on my onFling approach and implementation are:

  1. That is based on a threshold velocity
  2. Once initiated, the animation goes on and can’t be reversed
  3. The gesture is the trigger of a startActivity call and each of those activities are… activities!

Surely my onFling implementation was too dumb and since I read Rich’s post, I have the secret personal project to use the ViewPager and one of the underlying adapters to reimplement that ugly onFling part of our application.

Well, the time has come with the Christmas holidays!

The point is that my onFling-based activity is now composed by two fragment and the FragmentPagerAdapter is nice but manage only one Fragment as a ViewPager current page content.

Now, what if a want a vertical stack of several fragments? ⇒ Read and adapt the FragmentPagerAdapter source code from the Support Package (compact version without comments):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
public abstract class VerticalFragmentsPagerAdapter extends PagerAdapter {
    private static final String TAG = "VerticalFragmentsPagerAdapter";
    private static final boolean DEBUG = false;
    private final FragmentManager mFragmentManager;
    private final int mRows;
    private FragmentTransaction mCurTransaction = null;
    private ArrayList<Fragment> mCurrentPrimaryItem = null;

    public VerticalFragmentsPagerAdapter(FragmentManager fm, int rows) {
        mFragmentManager = fm;
        mRows = rows;
        mCurrentPrimaryItem = new ArrayList<Fragment>(mRows);
    }

    /**
     * Return the Fragments associated with a specified position.
     */
    public abstract ArrayList<Fragment> getItem(int position);

    @Override
    public void startUpdate(ViewGroup container) {}

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Context ctx = container.getContext();
        LayoutInflater inflater =
                (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View layout = inflater.inflate(R.layout.pager, null);
        layout.setId(6109 + position);
        LinearLayout ll = (LinearLayout) layout.findViewById(R.id.pager_linear_layout);
        ll.setId(900913 + position);
        ((ViewPager) container).addView(layout);
        Fragment firstFragment =
                mFragmentManager.findFragmentByTag(
                        makeFragmentName(container.getId(), position, 0));
        ArrayList<Fragment> frags = null;

        if (firstFragment != null) {
            frags = new ArrayList<Fragment>(mRows);

            for (int i = 0; i < mRows; ++i) {
                frags.add(mFragmentManager.findFragmentByTag(
                        makeFragmentName(container.getId(), position, i)));
            }

            for (Fragment f : frags) {
                if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + f);
                mCurTransaction.attach(f);
            }
        } else {
            frags = getItem(position);
            int currentRow = 0;

            for (Fragment f : frags) {
                if (DEBUG) Log.v(TAG, "instanciateItem / Adding item #" + position + ": f= " + f);
                mCurTransaction.add(ll.getId(), f,
                        makeFragmentName(container.getId(), position, currentRow));
                currentRow++;
            }
        }

        if (frags != mCurrentPrimaryItem) {
            for (Fragment f : frags) {
                f.setMenuVisibility(false);
                f.setUserVisibleHint(false);
            }
        }

        return layout;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        for (int i = 0; i < mRows; ++i) {
            if (DEBUG) Log.v(TAG, "destroyItem / Detaching item #" + position + ": row=" + i);
            mCurTransaction.detach(
                    mFragmentManager.findFragmentByTag(
                            makeFragmentName(container.getId(), position, i)));
        }
        ((ViewPager) container).removeView((View)object);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        ArrayList<Fragment> frags = new ArrayList<Fragment>(mRows);

        for (int i = 0; i < mRows; ++i) {
            frags.add(mFragmentManager.findFragmentByTag(
                    makeFragmentName(container.getId(), position, i)));
        }

        if (!frags.equals(mCurrentPrimaryItem)) {
            if (!mCurrentPrimaryItem.isEmpty() && (mCurrentPrimaryItem.get(0) != null)) {
                for (Fragment f : mCurrentPrimaryItem) {
                    f.setMenuVisibility(false);
                    f.setUserVisibleHint(false);
                }
            }

            if (!frags.isEmpty() && (frags.get(0) != null)) {
                for (Fragment f : frags) {
                    f.setMenuVisibility(true);
                    f.setUserVisibleHint(true);
                }
            }
            mCurrentPrimaryItem = (ArrayList<Fragment>) frags.clone();
            if (DEBUG) Log.v(TAG, "setPrimaryItem #" + position + ": " + mCurrentPrimaryItem);
        }
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitAllowingStateLoss();
            mCurTransaction = null;
            mFragmentManager.executePendingTransactions();
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) { return view == ((View) object); }

    @Override
    public Parcelable saveState() { return null; }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {}

    public static String makeFragmentName(int viewId, int index, int subindex) {
        return "android:switcher:" + viewId + ":" + index + ":" + subindex;
    }
}

This implementation uses ArrayList as the fragments’ container. The trick here is that instead of returning a fragment from instanciateItem, I return a containing a LinearLayout.

Be warned: don’t use several instances of the same fragment for a given position! That adapter is not designed to do that…

From the above code, it’s now trivial to figure out how to extends that adapter: “Uh… the getItem should return an ArrayList of Fragments?” − “Yup!”

A trivial example is available on github. Just clone and run ant debug && ant installd… Feel free to fork, improve and return your feedback.

Comments