1

I want to create a simple custom UI element in Android and let it flow from the right border to the left rim of the display. The custom UI element is a rectangle with a symbol in it. Unfortunately, when having creating a second UI element while the first is being animated, the shape of the first custom UI element shapes such that the symbol within the rectangle strangley disappears while the rectangle proceeds to flow from right to left.

Here you can see two sceenshots that were taken within a short period of time:

enter image description here

I use the code for the XML layout file advised here Android how to create simple custom UI elements.

Now I use an fragment Test with the thread handling class CountDownTimer and I create 2 objects from the custom Java class View_Game_Event_Rectangle that just stores information for the Custom UI element (but this class is not causing the problem). What I do in the fragment Test is to first create the custom UI element (50 timeslots before its predefined starting timeslot) and make the background invisible such that the user can't see it. Then I shift the custom UI element to the very right (40 timeslots before its predeined starting timeslot). And eventually (when its starting timeslot has come), I animate the view element from right to left on the screen and make the background visible. This works fine. But when the second UI element is created during the animation sequence of the first UI element, the first UI elemment changes its shape (no symbol visible any more within the rectangle) and I don't understand why this is happening.

Do you have any idea what I can do to force the two parts of the UI element to be together all the time?

Here you have the XML layout file of the custom UI element:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <corners android:radius="5dp" />
            <solid android:color="#FF9800" />
        </shape>
    </item>
    <item
        android:drawable="@drawable/ic_launcher_light"
        android:width="48dp"
        android:height="48dp"
        android:gravity="left|center_vertical" />
</layer-list>

and here you can see the commented fragment class Test with the relevant method updateScreen:

public class Test extends Fragment {


    /*
    Game variables
     */

    public static final int DELAY_IN_MILLIS = 100;
    public static final double DIFFICULTY_SPEED_MULTIPLICATOR_INCREMENT_PER_LEVEL = 0.3;
    public static final int TIME_OF_A_LEVEL_IN_SECONDS = 90;

    private int currentTimeLeftInTheLevel_MILLIS;
    private int currentTimeSlot;
    private boolean gameIsRunning;
    private Handler handler = new Handler();

    private double diffcultySpeedMultiplicator;

    private int timeslotsEventElementUntilTheMiddleOfTheScreen_Level1 = (int)(5.0 * (1000.0/DELAY_IN_MILLIS));
    private int numberOfMillisecondsUntilTheMiddleOfTheScreen_Level1 = 3000;




    //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;
    Drawable[] drawingsForTheViewEvents;
    int [] startingTimeSlotViewEvent;
    int [] durationViewEvent;
    ArrayList<View_Game_Event_Rectangle> arrayList_GameEventRectangles;


    private boolean fragmentViewHasBeenCreated = false;


    private CountDownTimer cdt;
    private  final long DURATION = 900000L; //40sec
    private  final long DELAY_COUNT_DOWN_TIMER = 50L; //100ms

    private int numberOfTimeSlotsUntilTheEndOfScreen = (int)(numberOfMillisecondsUntilTheMiddleOfTheScreen_Level1 * 2/(DELAY_COUNT_DOWN_TIMER));



    public Test() {
        // Required empty public constructor
    }


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

        //Set the startingTimeSlots and the durations of the 2 custom UI elements
        viewEvent = new View_Game_Event_Rectangle[2];
        drawingsForTheViewEvents = new Drawable[2];
        startingTimeSlotViewEvent = new int [2];
        durationViewEvent = new int [2];
        startingTimeSlotViewEvent [0] = 51;
        durationViewEvent [0] = 10;
        startingTimeSlotViewEvent [1] = 111;
        durationViewEvent [1] = 1;

        arrayList_GameEventRectangles = new ArrayList<View_Game_Event_Rectangle>();


    }

    @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();



    }


    public void startGame () {
        gameIsRunning = true;
        startRound();
    }

    public void startRound () {
        Log.e("LogTag_El", "-------Start Test Game------" );
        diffcultySpeedMultiplicator = 1;
        currentTimeSlot =0;

        //Create the array list with the Game_Event_Rectangles
        arrayList_GameEventRectangles.add(new View_Game_Event_Rectangle(getActivity(), "Bulb 1", startingTimeSlotViewEvent [0], durationViewEvent [0]));
        arrayList_GameEventRectangles.add(new View_Game_Event_Rectangle(getActivity(), "Bulb 1", startingTimeSlotViewEvent [1], durationViewEvent [1]));
        countDownTime();

    }

    //Method for updating the screen iteratively
    private void updateScreen() {

        //Iterate through all custom UI objects in the arrayList_GameEventRectangles in each time slot and see if an action has to be taken
        for (int currentElement =0; currentElement <arrayList_GameEventRectangles.size(); currentElement++) {

            //Create view, set its background and define the constraints for the Constraint Layout (50 timeslots before its starting TimeSlot)
            if (currentTimeSlot == arrayList_GameEventRectangles.get(currentElement).getStartingTimeSlot() - 50) {
                Log.e("LogTag_El", "Create view - currentTimeSlot: " + currentTimeSlot);
                Log.e("LogTag_El", "Create view - currentElement: " + currentElement);
                Log.e("LogTag_El", "Create view - getDuration: " + arrayList_GameEventRectangles.get(currentElement).getDuration());
                Log.e("LogTag_El", "Create view - getStartingTimeSlots: " + arrayList_GameEventRectangles.get(currentElement).getStartingTimeSlot());


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

                //Set the parameters and the background of the view element
                arrayList_GameEventRectangles.get(currentElement).setLayoutParams(new ViewGroup.LayoutParams(0, 0));
                arrayList_GameEventRectangles.get(currentElement).setBackground(ContextCompat.getDrawable(getActivity(),R.drawable.game_event_rectangle_bulb_1));


                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() / ((double) numberOfTimeSlotsUntilTheEndOfScreen));
                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 (40 timeslots before its starting TimeSlot)
            if (currentTimeSlot == arrayList_GameEventRectangles.get(currentElement).getStartingTimeSlot() - 40) {
                Log.e("LogTag_El", "Shift view - currentTimeSlot: " + currentTimeSlot);
                Log.e("LogTag_El", "Shift view - currentElement: " + currentElement);
                arrayList_GameEventRectangles.get(currentElement).setTranslationX(arrayList_GameEventRectangles.get(currentElement).getWidth());
            }


            //Animate view element from right to left on the screen and make the background visible (when its startingTimeSlot has come)
            if (currentTimeSlot ==arrayList_GameEventRectangles.get(currentElement).getStartingTimeSlot()) {
                Log.e("LogTag_El", "Animate view - currentTimeSlot: " + currentTimeSlot);
                Log.e("LogTag_El", "Animate view - currentElement: " + currentElement);
                arrayList_GameEventRectangles.get(currentElement).getBackground().setAlpha(255);
                arrayList_GameEventRectangles.get(currentElement).animate().setDuration(numberOfMillisecondsUntilTheMiddleOfTheScreen_Level1).translationX(widthDisplay*(-1)).start();

            }

            helpCounterUpdateScreen++;


        }
    }

    //Method for counting down the time using the thread handling class "CountDownTimer"
    private void countDownTime(){
        cdt = new CountDownTimer(DURATION, DELAY_COUNT_DOWN_TIMER) {
            boolean delay = true;
            public void onTick(long millisUntilFinished) {
                if(delay) {
                    delay = false;
                } else {
                    currentTimeLeftInTheLevel_MILLIS = currentTimeLeftInTheLevel_MILLIS - DELAY_IN_MILLIS;
                    helpCounterCountDownTime++;
                    currentTimeSlot++;

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

    }


    @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);
    }
}
VanessaF
  • 515
  • 11
  • 36
  • 1
    I haven't looked through your code, so this is just a guess. If you are reusing the drawable, you may need to mutate it so the two instances don't share the same state. See [Drawable.mutate](https://developer.android.com/reference/android/graphics/drawable/Drawable#mutate()). – Cheticamp Jul 17 '22 at 13:11
  • @Cheticamp: Thanks for your comment. Indeed, calling mutate helped to solve the problem. As far as I see it it is pretty strange that by default mutate is not activated causing big problems when you use the same drawables multiple times. – VanessaF Jul 19 '22 at 17:51
  • 1
    I believe that it's done for memory efficiency reasons. – Cheticamp Jul 19 '22 at 18:37

0 Answers0