Timmiej93
Timmiej93

Reputation: 1437

How can I get (or emulate) a ViewPager inside a DialogFragment?

I want to have a dialogFragment with a few pages, to enter some settings for my app. When I use AlertDialog.Builder, I get an IllegalStateException saying "Fragment does not have a view", which confuses me (also the stack is pretty useless).

I can get a popup working with the ViewPager in it, doing exactly what I want it to do, but it doesn't have the buttons I want. I really like the look of the default AlertDialog, so I'd preferably use that if possible (for consistency reasons). If not, I'd be okay with making a custom dialog that just looks exactly the same.

So, how can I get the ViewPager working in a AlertDialog / dialogFragment (or similar)?

EDIT: Just to clarify, it doesn't have to be an AlertDialog, but I would like the option to set a title, (permanent) message and buttons.

EDIT2: So it appears the "Fragment does not have a view" error occurs on returning f in FragmentFirst newInstance(). This should return a fragment to the getItem() method in the adapter, which should return the fragment associated with the correct position. I hope this helps anyone pinpointing what's going wrong.

EDIT3: Upon messing around with some stuff, I now got this error: No view found for id 0x7f0c00b7 (com.example.tim.timapp:id/pager) for fragment FragmentDefault{47febfa #1 id=0x7f0c00b7 android:switcher:2131493047:0}, basically telling me something is trying to find the pager view in the wrong fragment? Not quite sure how to fix that. NB. FragmentDefault is exactly the same as FragmentFirst, except for its name and some id's.

EDIT4: (11 Mar '16) A question: When using a newInstance method (like I do inside the fragments), should the returned fragment have a view? When I call if (f.getView() == null), it returns true, which makes me wonder if f even should have a view. If so, why doesn't it? Every newInstance method I see online has exactly the same layout as mine, so it doesn't look like I'm missing something.
Also, for some reason onCreateView in the fragments (FragmentFirst, FragmentSecond, etc.) is never reached. I have a Log.d right after the onCreateView, which never shows in the logcat. Any ideas?

Code:
Calling the dialogFragment

TestActivityFragment newFragment = new TestActivityFragment();  
newFragment.show(getChildFragmentManager(), "dialog");  

TestActivityFragment:

package com.example.fragments.MainFragments.Test;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v13.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.tim.timapp.R;

public class TestActivityFragment extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        LayoutInflater inflater = getActivity().getLayoutInflater();
        final View view = inflater.inflate(R.layout.test_dialog, null);

        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setView(view)
                .setPositiveButton("POS", null)
                .setNegativeButton("NEG", null)
                .setTitle("TEST_TITLE")
                .setMessage("TEST_MESSAGE");

        ViewPager mPager = (ViewPager) view.findViewById(R.id.pager);
        FragmentStatePagerAdapter mPagerAdapter = new TestAdapter(getChildFragmentManager());
        mPager.setAdapter(mPagerAdapter);

        return builder.create();
    }

    private class TestAdapter extends FragmentStatePagerAdapter {
        public TestAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            switch(position) {
                case 0: return FragmentFirst.newInstance("FragmentFirst, Instance 1");
                case 1: return FragmentSecond.newInstance("SecondFragment, Instance 1");
                case 2: return FragmentThird.newInstance("ThirdFragment, Instance 1");
                case 3: return FragmentThird.newInstance("ThirdFragment, Instance 2");
                default: return FragmentDefault.newInstance("DefaultFragment");
            }
        }

        @Override
        public int getCount() {
            return 5;
        }
    }
}

One of the fragments

package com.example.fragments.MainFragments.Test;

import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.tim.timapp.R;

public class FragmentFirst extends Fragment{

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.frag_first, container, false);

        TextView tv = (TextView) v.findViewById(R.id.tvFragFirst);
        tv.setText(getArguments().getString("msg"));

        return v;
    }

    public static FragmentFirst newInstance(String text) {

        FragmentFirst f = new FragmentFirst();
        Bundle b = new Bundle();
        b.putString("msg", text);

        f.setArguments(b);

        return f;
    }
}

XML's

test_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

(I've had the viewPager as parent layout as well, without anything in it, didn't change a thing)

frag_first.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tvFragFirst"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:text="TextView"
        android:textAppearance="?android:textAppearanceMedium"/>

</RelativeLayout>

Log

03-09 15:30:55.910 23343-23343/? I/art: Not late-enabling -Xcheck:jni (already on)
03-09 15:30:55.940 23343-23349/? E/art: Failed sending reply to debugger: Broken pipe
03-09 15:30:55.940 23343-23349/? I/art: Debugger is no longer active
03-09 15:30:55.950 23343-23343/? W/System: ClassLoader referenced unknown path: /data/app/com.example.tim.timapp-2/lib/x86_64
03-09 15:30:56.060 23343-23356/? D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
03-09 15:30:56.150 23343-23356/? I/OpenGLRenderer: Initialized EGL, version 1.4
03-09 15:30:56.210 23343-23356/? W/EGL_emulation: eglSurfaceAttrib not implemented
03-09 15:30:56.210 23343-23356/? W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0x7f5f5597db40, error=EGL_SUCCESS
03-09 15:30:58.870 23343-23343/com.example.tim.timapp D/GSF - getChildCount: 0
03-09 15:30:58.910 23343-23343/com.example.tim.timapp D/AndroidRuntime: Shutting down VM
03-09 15:30:58.910 23343-23343/com.example.tim.timapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.tim.timapp, PID: 23343
    java.lang.IllegalStateException: Fragment does not have a view
        at android.app.Fragment$1.onFindViewById(Fragment.java:2181)
        at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:963)
        at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)
        at android.app.BackStackRecord.run(BackStackRecord.java:793)
        at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1535)
        at android.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:562)
        at android.support.v13.app.FragmentStatePagerAdapter.finishUpdate(FragmentStatePagerAdapter.java:168)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1177)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1025)
        at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1545)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1465)
        at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1112)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:632)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1465)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:748)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:630)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at com.android.internal.policy.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2643)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2100)
        at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1191)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1452)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
        at android.view.Choreographer.doCallbacks(Choreographer.java:670)
        at android.view.Choreographer.doFrame(Choreographer.java:606)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
03-09 15:35:59.030 23343-23343/? I/Process: Sending signal. PID: 23343 SIG: 9

PS. If I've forgotten to add a piece of code, please let me know, so I can add it.

Upvotes: 4

Views: 3814

Answers (2)

Timmiej93
Timmiej93

Reputation: 1437

So, I finally managed to get stuff exactly like I wanted it. Quite some stuff has changed though.


The onCreateDialog() method hasn't changed, except for replacing return builder.create(); with return builder.show();, and some cosmetic changes.


The properly (cough) named TestAdapter is now a PagerAdapter instead of a FragmentStatePagerAdapter. It might not be pretty, but it works.

private class TestAdapter extends PagerAdapter {

    private int mCurrentPosition = -1;

    @Override
    public int getCount() {
        return pageAmount;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        LayoutInflater inflater = getActivity().getLayoutInflater();
        int layoutThingy;

        switch(position) {
            case 0:
                layoutThingy = R.layout.fragment_generalsettingsinputdialog1;
                break;
            case 1:
                layoutThingy = R.layout.fragment_generalsettingsinputdialog2;
                break;
            case 2:
                layoutThingy = R.layout.fragment_generalsettingsinputdialog3;
                break;
            default:
                layoutThingy = R.layout.fragment_viewpagererror;
        }

        View view = inflater.inflate(layoutThingy, null);
        container.addView(view);
        view.requestFocus();
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        ((ViewPager) container).removeView((View) object);
    }

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

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        super.setPrimaryItem(container, position, object);
        if (position != mCurrentPosition) {
            View view = (View) object;
            CustomPager pager = (CustomPager) container;
            if (view != null) {
                mCurrentPosition = position;
                pager.measureCurrentView(view);
            }
        }
    }
}

The fragments (those mentioned in the original post as One of the fragments have disappeared. They're not needed anymore.


The test_dialog.xml file has changed as well. It now contains a custom viewpager (and some page indicators, but this is not part of what was troubling me).

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true">

    <com.example.tim.timapp.CustomStuff.CustomPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:overScrollMode="never"/>

    <com.viewpagerindicator.CirclePageIndicator
        android:id="@+id/circlePageIndicator"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:fillColor="@color/colorPrimary"
        app:strokeColor="@color/lightGrey"/>

</LinearLayout>

To continue, the CustomPager looks like this:

package com.example.tim.timapp.CustomStuff;

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.View;

public class CustomPager extends ViewPager {

    private View mCurrentView;

    public CustomPager(Context context) {
       super(context);
    }

    public CustomPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mCurrentView == null) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }

        int height = 0;
        mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        int h = mCurrentView.getMeasuredHeight();
        if (h > height) height = h;
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    public void measureCurrentView(View currentView) {
        mCurrentView = currentView;
        requestLayout();
    }

    public int measureFragment(View view) {
        if (view == null)
            return 0;

        view.measure(0, 0);
        return view.getMeasuredHeight();
    }
}

It might look daunting, but it doesn't do much more then resizing the viewpager to the size of its current showing page. It doesn't look pretty with all kinds of animations and it's quite slow, but it does the job every time.


And finally, the frag_first.xml XML's. These can be any kind of layout XML you want, as long as it fits in the AlertDialog.


Just to (prove) show that it's working, here's an image.
If you need more info, feel free to ask. I've been struggling with this for a long time, so I'm happy to help.


(Click for larger version)

Upvotes: 4

Thilek
Thilek

Reputation: 676

I did something similar but rather then fragment i added custom views in the view pager.

        <android.support.v4.view.ViewPager
            android:id="@+id/view_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <package.CustomViewOne
                android:id="@+id/custom_view_one"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

            <package.CustomViewTwo
                android:id="@+id/custom_view_two"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

            <package.CustomViewThree
                android:id="@+id/custom_view_three"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

        </android.support.v4.view.ViewPager>

My adapter was like :

class SlidePagerAdapter extends PagerAdapter {

    private ViewPager viewPager;

    public SlidePagerAdapter(ViewPager viewPager) {
        this.viewPager = viewPager;
    }

    public View instantiateItem(ViewGroup collection, int position) {
        return viewPager.getChildAt(position);
    }

    @Override
    public int getCount() {
        return viewPager.getChildCount();
    }

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

    @Override
    public int getItemPosition(Object object) {

        for (int i = 0; i < getCount(); i++) {
            if (object == viewPager.getChildAt(i)) {
                return i;
            }
        }

        return POSITION_NONE;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        viewPager.removeView((View) object);
    }
}

Sample Custom view. (CustomViewOne.Java)

public class CustomViewOne extends LinearLayout {

    public CustomViewOne(Context context) {
        super(context);
        initLayout();
    }

    public CustomViewOne(Context context, AttributeSet attrs) {
        super(context, attrs);
        initLayout();
    }

    public CustomViewOne(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initLayout();
    }

    public void initLayout() {
        LayoutInflater.from(getContext()).inflate(R.layout.custom_view_one_layout, this);
    }
}

Upvotes: 0

Related Questions