Tuesday, 21 March 2023

How to precisely check the position of an animated element in Android

I want to develop a small game where an orange rectangle is animated from the right to the left on the display and when it is in the middle (green rectangle) the user should press a button and gets points if he/she succeeds (see Screenshot): enter image description here

Here is the code of the game class

public class Test extends Fragment implements View.OnClickListener {


    /*
    Game variables
     */

    public static final int DELAY_IN_MILLIS = 100;
    ArrayList<View_Game_Event_Rectangle> arrayList_GameEventRectangles;
    private int currentTimeSlot;
    private boolean gameIsRunning;
    private Handler handler = new Handler();
    private int timeslotsEventElementUntilTheMiddleOfTheScreen_Level1 = (int)(5.0 * (1000.0/DELAY_IN_MILLIS));
    private int numberOfMillisecondsUntilTheMiddleOfTheScreen_Level1 = 5000;

    //Fix parameters of the UI elements
    private float horizontalPercentageOfScoreRectangle_End = 0.491f;
    private float horizontalPercentageOfScoreRectangle_Start = 0.567f;
    private float verticalBiasOfEventElementToBeInTheLine = 0.049f;
    private float percentageHeightOfEventElement = 0.071f;


    int widthDisplay;
    int heightDisplay;
    int helpCounterRun =0;
    int helpCounterUpdateScreen =0;
    int helpCounterCountDownTime =0;

    private FragmentTestBinding binding;
    private ConstraintLayout constraintLayout;
    ConstraintSet constraintSet ;

    //Variables for the single view event
    View_Game_Event_Rectangle[] viewEvent;

    private boolean fragmentViewHasBeenCreated = false;
    private CountDownTimer cdt;
    private  final long DURATION = 900000L;
    private  final long DELAY_COUNT_DOWN_TIMER = 100;

    private int numberOfTimeSlotsUntilTheEndOfScreen = (int)(numberOfMillisecondsUntilTheMiddleOfTheScreen_Level1 * 2/(DELAY_COUNT_DOWN_TIMER));
    private int currentLevel;
    public Test() {
        // Required empty public constructor
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        arrayList_GameEventRectangles = new ArrayList<View_Game_Event_Rectangle>();
        currentLevel = 1;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        binding = FragmentTestBinding.inflate(inflater, container, false);

        WindowManager wm = (WindowManager) getActivity().getWindowManager();
        Display display = wm.getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        widthDisplay = size.x;
        heightDisplay = size.y;
        container.getContext();
        constraintLayout= binding.constraintLayout;
        fragmentViewHasBeenCreated = true;
        getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        constraintLayout = binding.constraintLayout;
        constraintSet = new ConstraintSet();
        startGame();
        return binding.getRoot();
    }


    @Override
    public void onClick(View view) {

    }


    public void startGame () {
        gameIsRunning = true;
        Log.e("LogTag_El", "-------Start Game------" );

        //Create rectangle-element that should be animated from the right to the left on the screen
        arrayList_GameEventRectangles.add(new View_Game_Event_Rectangle(getActivity(), "Bulb Rectangle", 100, 20));
        countDownTime();
    }


    private void updateScreen() {

        for (int currentElement =0; currentElement <arrayList_GameEventRectangles.size(); currentElement++) {

            //Create view and set
            if (currentTimeSlot == arrayList_GameEventRectangles.get(currentElement).getStartingTimeSlot() - 50) {

                arrayList_GameEventRectangles.get(currentElement).setActive(true);


                //Set the parameters and the backgrund of the view element
                arrayList_GameEventRectangles.get(currentElement).setLayoutParams(new ViewGroup.LayoutParams(0, 0));

                if(arrayList_GameEventRectangles.get(currentElement).getEventType().equals("Bulb Rectangle")) {
                    arrayList_GameEventRectangles.get(currentElement).setBackground(ContextCompat.getDrawable(getActivity(),R.drawable.game_event_rectangle_bulb_1).mutate());
                }

                arrayList_GameEventRectangles.get(currentElement).setId(View.generateViewId());

                //Make the view invisible (before it's appearence time)
                arrayList_GameEventRectangles.get(currentElement).getBackground().setAlpha(0);

                // Set the ConstraintLayout programatically for the view
                constraintLayout.addView(arrayList_GameEventRectangles.get(currentElement));
                constraintSet.clone(constraintLayout);
                constraintSet.constrainPercentHeight(arrayList_GameEventRectangles.get(currentElement).getId(), percentageHeightOfEventElement);

                float widthConstrainPercentage_element1 = (float)(arrayList_GameEventRectangles.get(currentElement).getDuration() / 100.0);
                float duration = arrayList_GameEventRectangles.get(currentElement).getDuration();

                constraintSet.constrainPercentWidth(arrayList_GameEventRectangles.get(currentElement).getId(), widthConstrainPercentage_element1);
                constraintSet.connect(arrayList_GameEventRectangles.get(currentElement).getId(),ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID,ConstraintSet.BOTTOM,0);
                constraintSet.connect(arrayList_GameEventRectangles.get(currentElement).getId(),ConstraintSet.TOP,ConstraintSet.PARENT_ID ,ConstraintSet.TOP,0);
                constraintSet.connect(arrayList_GameEventRectangles.get(currentElement).getId(),ConstraintSet.LEFT,ConstraintSet.PARENT_ID ,ConstraintSet.LEFT,0);
                constraintSet.connect(arrayList_GameEventRectangles.get(currentElement).getId(),ConstraintSet.RIGHT,ConstraintSet.PARENT_ID ,ConstraintSet.RIGHT,0);

                float horizontalBias = 1.0f ;
                constraintSet.setHorizontalBias(arrayList_GameEventRectangles.get(currentElement).getId(), horizontalBias);
                constraintSet.setVerticalBias(arrayList_GameEventRectangles.get(currentElement).getId(), verticalBiasOfEventElementToBeInTheLine);
                constraintSet.applyTo(constraintLayout);

            }


            //Shift the view to the right border of the display
            if (currentTimeSlot == arrayList_GameEventRectangles.get(currentElement).getStartingTimeSlot() - 40) {
                arrayList_GameEventRectangles.get(currentElement).setTranslationX(arrayList_GameEventRectangles.get(currentElement).getWidth());
            }


            //Animate view element
            if (currentTimeSlot ==arrayList_GameEventRectangles.get(currentElement).getStartingTimeSlot()) {
                arrayList_GameEventRectangles.get(currentElement).getBackground().setAlpha(255);
                long durationForTheAnimation = (long)(numberOfMillisecondsUntilTheMiddleOfTheScreen_Level1 * 1);

                arrayList_GameEventRectangles.get(currentElement).animate().setDuration(durationForTheAnimation).translationX(widthDisplay*(-1)).start();

            }
            helpCounterUpdateScreen++;
            checkPositionsOfActiveElements();

        }

    }

    private void countDownTime(){

        cdt = new CountDownTimer(DURATION, DELAY_COUNT_DOWN_TIMER) {
            boolean delay = true;
            public void onTick(long millisUntilFinished) {
                if(delay) {
                    delay = false;
                } else {

                    helpCounterCountDownTime++;
                    currentTimeSlot++;

                    updateScreen();
                    delay = true;
                }
            }
            public void onFinish() {
                updateScreen();
            }
        }.start();

    }

    public void checkPositionsOfActiveElements () {
        for (int currentElement =0; currentElement <arrayList_GameEventRectangles.size(); currentElement++) {
            //Check if the view is still running
            if (arrayList_GameEventRectangles.get(currentElement).isActive() == true && currentElement==0) {
                Log.e("LogTag_FGa_CP", "currentTimeSlot: " + currentTimeSlot);
                Log.e("LogTag_FGa_CP", "currentElement.getX(): " + arrayList_GameEventRectangles.get(currentElement).getX());
            }
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();

        // Reset your variable to false
        fragmentViewHasBeenCreated = false;

        // And clean up any postDelayed callbacks that are waiting to fire
        cdt.cancel();
        handler.removeCallbacksAndMessages(null);
    }
}

And here is the XML-layout file:

<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/constraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    tools:context=".MainActivity">


    <View
        android:id="@+id/imageView_TargetRectangle"
        android:layout_width="0dp"
        android:layout_height="0dp"


        android:background="@drawable/rectangle"
        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.16"
        app:layout_constraintHorizontal_bias="0.535"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.01"
        app:layout_constraintWidth_percent="0.10" />


    <Button
        android:id="@+id/button_action"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="Action"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.102"
        app:layout_constraintHorizontal_bias="0.373"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.745"
        app:layout_constraintWidth_percent="0.13" />

</androidx.constraintlayout.widget.ConstraintLayout>

To animate the view and to count down the time I use the handler CountDownTimer. Actually the animation itself works (I first shift the invisible orange rectangle to the very right and then make it visible after a certain time and animate it). To problem is, that I can not precisely determine where the orange rectangle is. I can only determine it after a certain timespan and this is not enough for what I want to do.

Here is the output of the LogTag:

2023-03-14 18:37:40.318 15544-15544/com.example.game E/LogTag_FGa_CP: currentTimeSlot: 100
2023-03-14 18:37:40.320 15544-15544/com.example.game E/LogTag_FGa_CP: currentElement.getX(): 2028.0
2023-03-14 18:37:40.617 15544-15544/com.example.game E/LogTag_FGa_CP: currentTimeSlot: 101
2023-03-14 18:37:40.618 15544-15544/com.example.game E/LogTag_FGa_CP: currentElement.getX(): 2013.0167
2023-03-14 18:37:40.857 15544-15544/com.example.game E/LogTag_FGa_CP: currentTimeSlot: 102
2023-03-14 18:37:40.857 15544-15544/com.example.game E/LogTag_FGa_CP: currentElement.getX(): 1972.158
2023-03-14 18:37:41.149 15544-15544/com.example.game E/LogTag_FGa_CP: currentTimeSlot: 103
2023-03-14 18:37:41.150 15544-15544/com.example.game E/LogTag_FGa_CP: currentElement.getX(): 1883.3052
2023-03-14 18:37:41.403 15544-15544/com.example.game E/LogTag_FGa_CP: currentTimeSlot: 104
2023-03-14 18:37:41.403 15544-15544/com.example.game E/LogTag_FGa_CP: currentElement.getX(): 1780.0686
2023-03-14 18:37:41.668 15544-15544/com.example.game E/LogTag_FGa_CP: currentTimeSlot: 105
2023-03-14 18:37:41.669 15544-15544/com.example.game E/LogTag_FGa_CP: currentElement.getX(): 1634.5706
2023-03-14 18:37:41.931 15544-15544/com.example.game E/LogTag_FGa_CP: currentTimeSlot: 106
2023-03-14 18:37:41.936 15544-15544/com.example.game E/LogTag_FGa_CP: currentElement.getX(): 1495.0557
2023-03-14 18:37:42.185 15544-15544/com.example.game E/LogTag_FGa_CP: currentTimeSlot: 107
2023-03-14 18:37:42.186 15544-15544/com.example.game E/LogTag_FGa_CP: currentElement.getX(): 1317.382
2023-03-14 18:37:42.481 15544-15544/com.example.game E/LogTag_FGa_CP: currentTimeSlot: 108
2023-03-14 18:37:42.481 15544-15544/com.example.game E/LogTag_FGa_CP: currentElement.getX(): 1088.4066
2023-03-14 18:37:42.760 15544-15544/com.example.game E/LogTag_FGa_CP: currentTimeSlot: 109

As you can see, the x-positions of the orange rectangle decrease by a huge amount of pixels when I check it (e.g. 1972.158, 1883.3052, 1780.0686). What I actually want to have is a method, that can return each of the positions of the orange rectangle, meaning: 1972, 1971, 1970, 1969 etc.. This way, I could make sure - after pressing the button - to get the exact position of the orange rectangle.

Reminder: Would anyone mind sharing his/her knowledge about how to check the positions precisely? I'll highly appreciate every input as I tried a lot and don't know how to proceed.



from How to precisely check the position of an animated element in Android

No comments:

Post a Comment