Search code examples
javaandroidandroid-viewpagersurfaceview

Surface View squashed inside view pager


I have a problem where I have a surface view inside a view pager. Everything renders fine until I press on one of the tabs. The view pager scrolls, but the page on the next screen is squashed.

enter image description here

enter image description here

If I exit the app and go back in it, the second page will render fine. Then if I press to go back to the first it squashes again.

I don't know at all what part of the layout is going wrong, and as far as I am aware, the actual height of the surface view is not changing, as when I use the .getHeight() method on the surface view it always returns the same value.

Here is my view pager class:

public class PendulumViewPager extends ViewPager {


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

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

@Override
public boolean onTouchEvent(MotionEvent ev) {
    return false;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}

Here is the fragment being added to the view pager:

public class SurfacePanelFragment extends Fragment{

private int TYPE = 1;
private SurfacePanel panel;
public void setType(int type){
    TYPE = type;
}

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

    panel = view.findViewById(R.id.canvas);
    panel.setType(TYPE);
    return view;
}

public void startStopPendulum(ImageButton view){
    panel.startStop(view);
}

public void stopPendulum(){
    panel.stop();
}

@Override
public void onResume() {
    if(getUserVisibleHint()) {
        panel.delayStartThread();
        ((MainActivity)getActivity()).getStopButton().setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_pause_white_48dp));
    }
    super.onResume();
}

@Override
public void onPause() {
    panel.terminate();
    super.onPause();
}

public void stopRendering(){
    panel.stopRendering();
}

public void startRendering(){
    panel.startRendering();
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if(panel != null) {
        if (isVisibleToUser) {
            panel.delayStartThread();
            ((MainActivity)getActivity()).getStopButton().setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_pause_white_48dp));
            Log.i("WP", String.valueOf(panel.getHeight()));
        } else {
            panel.terminate();
        }
    }
}

And here is the main activity:

public class MainActivity extends AppCompatActivity {

private PendulumViewPager pager;
private ArrayList<SurfacePanelFragment> fragments = new ArrayList<SurfacePanelFragment>(2);

private ImageButton stopButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayShowTitleEnabled(false);

    pager = findViewById(R.id.view_pager);
    pager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
        @Override
        public Fragment getItem(int position) {
            SurfacePanelFragment frag = new SurfacePanelFragment();
            switch (position) {
                case 0:
                    frag.setType(SurfacePanel.SINGLE_PENDULUM);

                    fragments.add(0, frag);
                    return frag;
                case 1:
                    frag.setType(SurfacePanel.DOUBLE_PENDULUM);
                    fragments.add(1, frag);
                    return frag;
                default:
                    return null;
            }
        }

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

        @Override
        public CharSequence getPageTitle(int position) {
            switch (position){
                case 0: return "Single";
                case 1: return "Double";
                default: return "";
            }
        }
    });

    TabLayout pagerTabs = findViewById(R.id.pager_tabs);
    pagerTabs.setTabGravity(TabLayout.GRAVITY_FILL);
    pagerTabs.setupWithViewPager(pager);

    stopButton = findViewById(R.id.stop_button);
    stopButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            startStopCurrentPendulum((ImageButton) view);
        }
    });

    ImageButton helpButton = findViewById(R.id.help_button);
    helpButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            startHelpActivity();
        }
    });
}

private void startStopCurrentPendulum(ImageButton view) {
    fragments.get(pager.getCurrentItem()).startStopPendulum(view);
}

public ImageButton getStopButton(){
    return stopButton;
}

private void startHelpActivity(){
    Intent intent = new Intent(this, HelpActivity.class);
    startActivity(intent);
}

Here is the SurfacePanel class

public class SurfacePanel extends SurfaceView implements SurfaceHolder.Callback {

public static final int SINGLE_PENDULUM = 1;
public static final int DOUBLE_PENDULUM = 2;

private int TYPE = 1;

private PendulumThread _thread;
private boolean needToStartThread = false;

private SurfaceHolder holder;

public SurfacePanel(Context context, AttributeSet attrSet) {
    super(context, attrSet);
    SurfaceHolder holder = getHolder();
    holder.addCallback(this);
}


@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
    Log.i("WP", String.valueOf(this.getHeight()));
    this.holder = surfaceHolder;
    if(needToStartThread){
        startThread();
        needToStartThread = false;
    }
}

@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

}

@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
    if(_thread != null) {
        try {
            _thread.terminate();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    this.holder = null;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (_thread != null) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            _thread.click(event);
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            _thread.updatePos(event);
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            _thread.unclick();
        }
    }
    return true;
}

public void startStop(ImageView button) {
    if(_thread != null){
        if(_thread.isPRunning()){
            button.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_play_arrow_white_48dp));
            _thread.pStop();
        }else{
            button.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_pause_white_48dp));
            _thread.pStart();
        }
    }
}

public void start(){
    if(_thread != null){
        _thread.pStart();
    }
}

public void stop(){
    if(_thread != null){
        _thread.pStop();
    }
}

public void stopRendering(){
    if(_thread != null){
        Log.i("WP", "A");
        _thread.stopRendering();
    }
}

public void startRendering(){
    if(_thread != null){
        Log.i("WP", "D");
        _thread.startRendering();
    }
}

public void terminate(){
    if(_thread != null){
        try {
            _thread.terminate();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

private void startThread(){
    if(_thread == null || !_thread.isAlive()) {
        switch (TYPE) {
            case SurfacePanel.SINGLE_PENDULUM:
                _thread = new SinglePendulumThread(holder, getWidth(), getHeight(), getContext());
                _thread.pStart();
                _thread.start();
                break;
            case SurfacePanel.DOUBLE_PENDULUM:
                _thread = new DoublePendulumThread(holder, getWidth(), getHeight(), getContext());
                _thread.pStart();
                _thread.start();
                break;
            default:
                _thread = new SinglePendulumThread(holder, getWidth(), getHeight(), getContext());
                _thread.pStart();
                _thread.start();
                break;
        }
    }
}

public void delayStartThread(){
    if(holder == null) {
        needToStartThread = true;
    }else{
        startThread();
    }
}

public void setType(int type){
    TYPE = type;
}

And here is the surface panel fragment with the surface panel view in

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<com.astarphysics.wikidpendulum.SurfacePanel
    android:id="@+id/canvas"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:layout_editor_absoluteX="8dp"
    tools:layout_editor_absoluteY="8dp" />
</RelativeLayout>

This is the single pendulum thread

public class SinglePendulumThread extends PendulumThread{

private SurfaceHolder holder;
private Paint paint;
private int backgroundColor;
private int width;
private Context context;

private boolean clicked = false;

private static final double g = 9.81;
private final int RADIUS;

private int length;
private double theta = Math.PI/8;
private double thetaV = 0;

SinglePendulumThread(SurfaceHolder holder, int width, int height, Context context){
    this.holder = holder;
    this.context = context;

    int bobColor = context.getResources().getColor(R.color.colorBob);
    int shadowColor = context.getResources().getColor(R.color.shadowColor);
    backgroundColor = context.getResources().getColor(R.color.backgroundColor);
    paint = new Paint();
    paint.setColor(bobColor);
    paint.setShadowLayer(5, -1, 2, shadowColor);

    this.width = width;
    RADIUS = dpToPx(20);
    length = dpToPx(200);
}

protected void update(){
    if(pRunning && !clicked) {
        thetaV += (-1 * g * Math.sin(theta)) / length;
        theta += thetaV * (40 * Math.pow(10, -2));
    }
}

protected void render(){
    Canvas canvas = holder.lockCanvas();
    canvas.drawColor(backgroundColor);

    canvas.drawLine(width / 2, 0, (float) ((width / 2) + (length * Math.sin(theta))), (float) (length * Math.cos(theta)), paint);
    canvas.drawCircle((int) Math.round((width / 2) + (length * Math.sin(theta))), (int) Math.round(length * Math.cos(theta)), RADIUS, paint);

    holder.unlockCanvasAndPost(canvas);
}

@Override
void click(MotionEvent e) {
    clicked = true;
    updatePos(e);
}

@Override
void updatePos(MotionEvent e){
    length = (int)Math.round(Math.sqrt((Math.abs(e.getX()-(width/2))*Math.abs(e.getX()-(width/2)))+(e.getY()*e.getY())));
    theta = Integer.signum(Math.round((e.getX()-(width/2))))*Math.asin((Math.abs(e.getX()-(width/2))/length));
}

@Override
void unclick() {
    this.resetForPStart();
    clicked = false;
}

private void resetForPStart(){
    thetaV = 0;
}

private int dpToPx(int dp) {
    DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
    return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
}

and this is the double pendulum thread:

public class DoublePendulumThread extends PendulumThread {

private SurfaceHolder holder;
private Paint paint;
private int backgroundColor;
private int width;
private int height;
private Context context;

private boolean clicked1 = false;
private boolean clicked2 = false;

private final int RADIUS;
private final double m1 = 3;
private final double m2 = 3;
private static final double g = 9.81;

private double l1;
private double l2;

private double[] state = new double[4];
private double[] dydt = new double[4];
private double[] prev = new double[4];
private double[] midpt = new double[4];
private static final double h = 0.5;
private static final double DIMENSION = 4;
private static final double HALF = h/2;
private static final double THIRD = h/3;
private static final double SIXTH = h/6;


DoublePendulumThread(SurfaceHolder holder, int width, int height, Context context){
    this.holder = holder;
    this.context = context;
    this.width = width;
    this.height = height;

    //Setting sizes that require Context to be set
    RADIUS = dpToPx(20);
    l1 = dpToPx(150);
    l2 = dpToPx(150);
    state[0] = Math.PI/8;

    //Setting up paints for rendering on Canvas
    int bobColor = context.getResources().getColor(R.color.colorBob);
    int shadowColor = context.getResources().getColor(R.color.shadowColor);
    backgroundColor = context.getResources().getColor(R.color.backgroundColor);
    paint = new Paint();
    paint.setColor(bobColor);
    paint.setShadowLayer(5, -1, 2, shadowColor);
}

/*
Evaluates derivatives for Runge-Kutta.
*/
private void loadDys(double[] y){
    dydt[0] = y[2]; //Theta1
    dydt[1] = y[3]; //Theta2

    dydt[2] = ( -1*g*(2*m1 + m2)*sin(y[0]) - m2*g*sin(y[0] - 2*y[1]) - 2*sin(y[0] - y[1])*m2*(y[3]*y[3]*l2 + y[2]*y[2]*l1*cos(y[0] - y[1])) ) / (l1*(2*m1 + m2 - m2*cos(2*y[0]-2*y[1])));
    dydt[3] = (2*sin(y[0] - y[1])*(y[2]*y[2]*l1*(m1+m2) + g*(m1+m2)*cos(y[0]) + y[3]*y[3]*l2*m2*cos(y[0]-y[1]))) / (l2*(2*m1 + m2 - m2*cos(2*y[0]-2*y[1])));
}

/*
Each time update is run one iteration of the Runge-Kutta method takes place, with time step h.
*/
protected void update(){
    if(pRunning && !clicked1 && !clicked2) {
        for (int i = 0; i < DIMENSION; i++) {
            midpt[i] = state[i];
            prev[i] = state[i];
        }

        loadDys(midpt);
        for (int i = 0; i < DIMENSION; i++) {
            state[i] += SIXTH * dydt[i];
            midpt[i] = prev[i] + HALF * dydt[i];
        }
        loadDys(midpt);
        for (int i = 0; i < DIMENSION; i++) {
            state[i] += THIRD * dydt[i];
            midpt[i] = prev[i] + HALF * dydt[i];
        }
        loadDys(midpt);
        for (int i = 0; i < DIMENSION; i++) {
            state[i] += THIRD * dydt[i];
            midpt[i] = prev[i] + h * dydt[i];
        }
        loadDys(midpt);
        for (int i = 0; i < DIMENSION; i++) {
            state[i] += SIXTH * dydt[i];
        }
    }else if(pRunning && clicked1) {
        state[3] += (-1 * g * Math.sin(state[1])) / l2;
        state[3] = state[3] * 0.9;
        state[1] += state[3] * (40 * Math.pow(10, -2));
    }
}

protected void render(){
    Canvas canvas = holder.lockCanvas();
    canvas.drawColor(backgroundColor);

    canvas.drawLine(width / 2, height/4, (float) ((width / 2) + (l1 * Math.sin(state[0]))), (float) (height/4 + l1 * Math.cos(state[0])), paint);
    canvas.drawLine((float) ((width / 2) + (l1 * Math.sin(state[0]))), (float) (height/4 + l1 * Math.cos(state[0])), (float) ((width / 2) + (l1 * Math.sin(state[0])) + (l2 * Math.sin(state[1]))), (float) (height/4 + l1 * Math.cos(state[0]) + l2 * Math.cos(state[1])), paint);
    canvas.drawCircle((float) ((width / 2) + (l1 * Math.sin(state[0]))), (float) (height/4 + l1 * Math.cos(state[0])), RADIUS, paint);
    canvas.drawCircle((float) ((width / 2) + (l1 * Math.sin(state[0])) + (l2 * Math.sin(state[1]))), (float) (height/4 + l1 * Math.cos(state[0]) + l2 * Math.cos(state[1])), RADIUS, paint);

    holder.unlockCanvasAndPost(canvas);
}

void updatePos(MotionEvent e){
    if(clicked1){
        l1 = (int)Math.round(Math.sqrt(((e.getX()-(width/2))*(e.getX()-(width/2)))+((e.getY()-height/4)*(e.getY()-height/4))));
        if(e.getY() - height/4 < 0) {
            if (Math.round(e.getX() - width / 2) == 0) {
                state[0] = Math.PI;
            } else {
                state[0] = Integer.signum(Math.round((e.getX() - (width / 2)))) * Math.acos((Math.abs(e.getX() - (width / 2)) / l1)) + Integer.signum(Math.round((e.getX() - (width / 2)))) * Math.PI / 2;
            }
        }else if(Math.round(e.getY() - (height/4)) == 0){
            state[0] = Integer.signum(Math.round(e.getX() - (width/2))) * Math.PI / 2;
        }else{
            state[0] = Math.asin((e.getX()-(width/2))/l1);
        }
    }else if(clicked2){
        l2 = (int)Math.round(Math.sqrt(((e.getX()-((width / 2) + (l1 * Math.sin(state[0]))))*(e.getX()-((width / 2) + (l1 * Math.sin(state[0])))))+((e.getY()-(height/4 + l1 * Math.cos(state[0])))*(e.getY()-(height/4 + l1 * Math.cos(state[0]))))));
        if(e.getY() - (height/4 + l1 * Math.cos(state[0])) < 0){
            if(Math.round(e.getX() - ((width/2) + l1*sin(state[0]))) == 0){
                state[1] = Math.PI;
            }else {
                state[1] = Integer.signum((int) Math.round((e.getX() - ((width / 2) + (l1 * Math.sin(state[0])))))) * Math.acos((Math.abs(e.getX() - ((width / 2) + (l1 * Math.sin(state[0])))) / l2)) + Integer.signum((int) Math.round((e.getX() - ((width / 2) + (l1 * Math.sin(state[0])))))) * Math.PI / 2;
            }
        }else if(Math.round(e.getY() - (height/4 + l1 * Math.cos(state[0]))) == 0) {
            state[1] = Integer.signum((int) Math.round(e.getX() - ((width/2) + l1*sin(state[0])))) * Math.PI / 2;
        }else{
            state[1] = Math.asin((e.getX()-((width / 2) + (l1 * Math.sin(state[0]))))/l2);
        }
    }
}

void click(MotionEvent e){
    if((e.getX() - ((width / 2) + (l1 * Math.sin(state[0])) + (l2 * Math.sin(state[1]))))*(e.getX() - ((width / 2) + (l1 * Math.sin(state[0])) + (l2 * Math.sin(state[1])))) + (e.getY() - (height/4 + l1 * Math.cos(state[0]) + l2 * Math.cos(state[1])))*(e.getY() - (height/4 + l1 * Math.cos(state[0]) + l2 * Math.cos(state[1]))) <= RADIUS*RADIUS){
        clicked2 = true;
    }else {
        clicked1 = true;
    }
    updatePos(e);
}

void unclick(){
    if(clicked1 || clicked2) {
        state[2] = 0;
        state[3] = 0;
        for (int i = 0; i < DIMENSION; i++) {
            dydt[i] = 0;
            midpt[i] = 0;
            prev[i] = 0;
        }
        clicked1 = false;
        clicked2 = false;
    }
}

private int dpToPx(int dp) {
    DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
    return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
}

private static double sin(double x){
    return Math.sin(x);
}
private static double cos(double x){
    return Math.cos(x);
}

Here is the main activity in which the fragment is embedded into the view pager.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
tools:context="com.astarphysics.wikidpendulum.MainActivity"
>

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <com.astarphysics.wikidpendulum.PendulumViewPager
        android:id="@+id/view_pager"
        android:layout_width="0dp"
        android:layout_height="0dp"
        tools:layout_constraintTop_creator="1"
        tools:layout_constraintRight_creator="1"
        tools:layout_constraintBottom_creator="1"
        android:layout_marginStart="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginEnd="8dp"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginTop="8dp"
        tools:layout_constraintLeft_creator="1"
        android:layout_marginBottom="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp">

    </com.astarphysics.wikidpendulum.PendulumViewPager>
</android.support.constraint.ConstraintLayout>

<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">

    <android.support.v7.widget.Toolbar
        app:layout_scrollFlags="enterAlways"
        android:id="@+id/toolbar"
        android:layout_height="192dp"
        android:layout_width="match_parent"
        app:contentInsetLeft="0dp"
        app:contentInsetStart="0dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="144dp">
                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentEnd="true"
                    android:layout_alignParentRight="true"
                    android:padding="10dp">

                <android.support.v7.widget.AppCompatImageButton
                    android:id="@+id/stop_button"
                    android:layout_width="48dp"
                    android:layout_height="48dp"
                    app:srcCompat="@drawable/ic_pause_white_48dp"
                    android:background="?attr/selectableItemBackgroundBorderless"
                    android:layout_marginRight="10dp"
                    android:layout_marginEnd="10dp"/>

                <android.support.v7.widget.AppCompatImageButton
                    android:id="@+id/help_button"
                    android:layout_width="48dp"
                    android:layout_height="48dp"
                    app:srcCompat="@drawable/ic_help_white_48dp"
                    android:background="?attr/selectableItemBackgroundBorderless" />

                </LinearLayout>

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center"
                    android:orientation="vertical">

                    <ImageView
                        android:layout_width="80dp"
                        android:layout_height="80dp"
                        android:src="@drawable/icon"/>

                    <TextView
                        android:layout_width="150dp"
                        android:layout_height="wrap_content"
                        android:text="@string/app_name"
                        android:gravity="center"
                        android:textSize="24sp"
                        android:fontFamily="sans-serif-smallcaps"
                        android:textColor="@color/textColor"/>

                </LinearLayout>
            </RelativeLayout>

            <android.support.design.widget.TabLayout
                android:id="@+id/pager_tabs"
                android:layout_width="match_parent"
                android:layout_height="48dp">

            </android.support.design.widget.TabLayout>

        </LinearLayout>
    </android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>


</android.support.design.widget.CoordinatorLayout>

If you think it is something else then I can add the code.

Thanks.


Solution

  • Simply changing pages in the ViewPager does not force the fragments to go through their life cycles or cause a re-creation of the SurfaceView. Hitting recents or home and then going back to the app does cause the SurfaceViews to be recreated and that seems to correct the problem that you are seeing.

    In case the problem is a subtle issue related to your layout, I suggest that you simplify it as much as possible. I have reworked the two main layouts to simplify them as you can see below. If you decide to go this route, you may need to change a few things in the main layout to get the sizing/positioning the way you want. You should not have to do any structural changes.

    Something else to try if the above doesn't work is to force re-creation of a fragment whenever it is selected for API 26+. This will force a re-instantiation of the fragment and the layout and should correct the problem.

    If the new layouts work, that would be best; if you have to do a re-creation upon tab selection, that is just a work-around. If you find out what the underlying problem is, please post it here. I would be interested in knowing what it is. Maybe another reader of this question can get to the bottom of it.

    pendulum_layout.xml

    <LinearLayout 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:orientation="vertical"
        tools:context=".MainActivity">
    
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="144dp"
            app:contentInsetLeft="0dp"
            app:contentInsetStart="0dp">
    
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    
                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentEnd="true"
                    android:layout_alignParentRight="true"
                    android:padding="10dp">
    
                    <android.support.v7.widget.AppCompatImageButton
                        android:id="@+id/stop_button"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:layout_marginEnd="10dp"
                        android:layout_marginRight="10dp"
                        android:background="?attr/selectableItemBackgroundBorderless"
                        app:srcCompat="@drawable/ic_pause_white_48dp" />
    
                    <android.support.v7.widget.AppCompatImageButton
                        android:id="@+id/help_button"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:background="?attr/selectableItemBackgroundBorderless"
                        app:srcCompat="@drawable/ic_help_white_48dp" />
    
                </LinearLayout>
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center"
                    android:orientation="vertical">
    
                    <ImageView
                        android:layout_width="80dp"
                        android:layout_height="80dp"
                        android:src="@drawable/icon" />
    
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:fontFamily="sans-serif-smallcaps"
                        android:gravity="center"
                        android:text="@string/app_name"
                        android:textColor="#FF000000"
                        android:textSize="24sp" />
    
                </LinearLayout>
            </RelativeLayout>
        </android.support.v7.widget.Toolbar>
    
        <com.example.viewpagerbase.PendulumViewPager
            android:id="@+id/view_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.design.widget.TabLayout
                android:id="@+id/pager_tabs"
                android:layout_width="match_parent"
                android:layout_height="48dp" />
        </com.example.viewpagerbase.PendulumViewPager>
    </LinearLayout>
    

    fragment_main.xml

        <com.example.viewpagerbase.MySurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    

    Here is one way to force a new fragment when selected. There are other methods available online if you search for them.

    Add the following to your ViewPager and call from each constructor. This will set up a page listener for API 26+. This listener will fire whenever a different page is selected.

    private void init() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            return;
        }
        addOnPageChangeListener(new OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }
    
            @Override
            public void onPageSelected(int position) {
                MainActivity.SectionsPagerAdapter adapter =
                    // Your adapter name goes here
                    ((MainActivity.SectionsPagerAdapter) getAdapter());
                adapter.invalidateFragment(position);
            }
    
            @Override
            public void onPageScrollStateChanged(int position) {
            }
        });
    }
    

    Add the following to your adapter:

    private List<SurfacePanelFragment> fragments = new ArrayList<>(PAGE_COUNT);
    
        @Override
        public SurfacePanelFragment getItem(int position) {
            if (fragments.get(position) == null) {
                fragments.set(position, SurfacePanelFragment.newInstance());
            }
            return fragments.get(position);
        }
    
        @Override
        public int getItemPosition(Object object) {
            return (fragments.contains(object)) ? POSITION_UNCHANGED : POSITION_NONE;
        }
    
        public void invalidateFragment(int position) {
            fragments.set(position, null);
            notifyDataSetChanged();
        }
    
        private static final int PAGE_COUNT = 2;