Retaining Fragment with Custom View

2019-09-07 03:30发布

问题:

I am writing a small animation project. I was having the issue that on screen orientation change, it was crashing. But I figured it out, so posting whole code here in the case someone needs something similar. Last sentence at the bottom explains a small issue I observed.

In this project, I have main activity hosting a fragment. And the fragment is hosing a custom Bubble view (Bubble extends View).

Main activity layout activity_main.xml is:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/bubble_fragment">

</LinearLayout> 

, and main activity MainActivity.java is:

public class MainActivity extends AppCompatActivity {

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

        String tag = "bubble_fragment_tag";
        FragmentManager fm = getFragmentManager();
        if (fm.findFragmentByTag(tag) == null){
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            BubbleFragment bubbleFragment = new BubbleFragment();
            ft.add(R.id.bubble_fragment, bubbleFragment, tag);
            ft.commit();
        }
    }
}

So, my main activity host a fragment with custom view in it. The fragment layout bubble_fragment.xml is:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:background="#ffcc33">

    <study.android.dino.testsolar.Bubble
        android:id="@+id/bubbleAnimationView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ff1122"
        android:padding="20dp"/>

    <!-- since parent is FrameLayout, this view will be stacked up in z-order -->
    <LinearLayout
        android:id="@+id/buttonsView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="right|bottom"
        android:layout_margin="10dp">

        <ImageButton
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"
            android:background="@null"/>
        <ImageButton
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"
            android:background="@null"/>
        <ImageButton
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"
            android:background="@null"/>
    </LinearLayout>
</FrameLayout>

, and my BubbleFragment.java file is: UPDATE: This is updated BubbleFragment that resolves the problem

public class BubbleFragment extends Fragment {

    View view;
    Bubble bubble;
    FrameLayout parent;
    LinearLayout floater;


    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        if (bubble == null){
            view =  inflater.inflate(R.layout.bubble_fragment, container, false); 
            parent = (FrameLayout) view;
        }

        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState){
        super.onActivityCreated(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onAttach(Activity activity){
        super.onAttach(activity);

        if (parent != null){
            parent.addView(bubble);
            parent.addView(floater);
        }
    }

    @Override
    public void onDetach(){
        super.onDetach();
        bubble = (Bubble) view.findViewById(R.id.bubbleAnimationView);
        floater = (LinearLayout) parent.findViewById(R.id.buttonsView);
        parent.removeView(bubble);
        parent.removeView(floater);
    }
}

This fragment holds custom view Bubble which is just a bubble drawing on Canvas and is not important here; hence not full code for it provided:

public class Bubble extends View {

    private static final boolean BUBBLING = true; //thread is running to draw

    private Paint paint;
    private ShapeDrawable bubble;

    // coordiantes, radius etc
    private int x;
    private int y;
    private int dx;
    private int dy;
    private int r;
    private int w = 400;
    private int h = 400;
    private int speed = 200;

    //handler to invalidate (force redraw on main GUI thread from this thread)
    private Handler handler = new Handler();

    public Bubble(Context context, AttributeSet attributesSet) {
        super(context, attributesSet);

        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        w = size.x;
        h = size.y;

        x = w/2;
        y = h/2;
        r = 60;
        dx = 1;
        dy = 1;

        bubble = new ShapeDrawable(new OvalShape());
        bubble.getPaint().setColor(Color.RED);
        bubble.setBounds(0, 0, r, r);

        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(10);
    }

    @Override
    protected void onSizeChanged  (int w, int h, int oldw, int oldh){
        //set bubble parameters (center, size, etc)

        startAnimation();
    }

    public void startAnimation(){
        new Thread(new Runnable() {
            public void run() {
                while (BUBBLING) {
                    moveBubble();

                    try {
                        Thread.sleep(speed);
                    } catch (InterruptedException e) {

                    }

                    //update by invalidating on main UI thread
                    handler.post(new Runnable() {
                        public void run() {
                            invalidate();
                        }
                    });
                }
            }
        }).start();
    }


    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();
        // draws bubble on canvas
        canvas.translate(dx, dy);
        bubble.draw(canvas);
        canvas.restore();
    }

    private void moveBubble(){
        dx += 1;
        dy += 1;
        x = x + dx;
        y = y + dy;
        if (bubble.getPaint().getColor() == Color.YELLOW){
            bubble.getPaint().setColor(Color.RED);
        } else {
            bubble.getPaint().setColor(Color.YELLOW);
        }
    }
}

This all works and my bubble draws and moves on the screen and orientation works as well. The only 'small' issue I observed is that speed at which bubble is drawn is set to 200ms as per code above. However, when I change orientation, the bubble is blinking faster although tracing code reviled that speed is still 200. Not sure why that is happening.

Thanks

回答1:

I updated my original post to show solution.