I have the following Java code
package com.example.game;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import com.example.game.databinding.FragmentTestBinding;
import java.util.ArrayList;
public class Test extends Fragment {
/*
Game variables
*/
private Handler handler = new Handler();
private int numberOfMillisecondsUntilTheMiddleOfTheScreen_Level1 = 8000;
private int currentTimeSlot;
private float verticalBiasOfEventElementToBeInTheLine = 0.049f;
private float percentageHeightOfEventElement = 0.071f;
int widthDisplay;
int heightDisplay;
//Type of View_Game_Events
public static final String VIEW_EVENT_RECTANGLE_SOLAR = "Solar";
private FragmentTestBinding binding;
private ConstraintLayout constraintLayout;
ConstraintSet constraintSet ;
//Variables for the single view event
View_Game_Event_Rectangle[] viewEvent;
boolean [] isViewEventActive;
Drawable[] drawingsForTheViewEvents;
private static int nextFreeIndexForViewEvent;
private static int numberOfViewEventInArray = 10;
ArrayList<View_Game_Event_Rectangle> arrayList_GameEventRectangles;
private int [] orangeRectangleValuesForTheLevel;
private boolean fragmentViewHasBeenCreated = false;
private CountDownTimer cdt;
private final long DELAY_COUNT_DOWN_TIMER = 100; //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);
nextFreeIndexForViewEvent = 0;
viewEvent = new View_Game_Event_Rectangle[numberOfViewEventInArray];
drawingsForTheViewEvents = new Drawable[numberOfViewEventInArray];
arrayList_GameEventRectangles = new ArrayList<View_Game_Event_Rectangle>();
isViewEventActive = new boolean[numberOfViewEventInArray];
}
@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();
//Define the repeatListener
binding.repeatButton.setOnTouchListener((View.OnTouchListener) new RepeatListener(30, 30, new View.OnClickListener() {
@Override
public void onClick(View view) {
View_Game_Event_Rectangle activeElement = null;
for (int currentElement =0; currentElement <arrayList_GameEventRectangles.size(); currentElement++) {
activeElement = arrayList_GameEventRectangles.get(currentElement);
}
if (activeElement!=null) {
activeElement.setBackground(ContextCompat.getDrawable(getActivity(),R.drawable.game_event_rectangle_solar_2).mutate());
// Schedule the change back to R.drawable.game_event_rectangle_solar_1 after 10 milliseconds
Handler handler = new Handler();
View_Game_Event_Rectangle finalActiveElement = activeElement;
handler.postDelayed(new Runnable() {
@Override
public void run() {
// Change the background back to R.drawable.game_event_rectangle_solar_1
finalActiveElement.setBackground(ContextCompat.getDrawable(getActivity(), R.drawable.game_event_rectangle_solar_1).mutate());
}
}, 300);
}
}
}));
startGame();
return binding.getRoot();
}
public void startGame () {
startRound();
}
public void startRound () {
orangeRectangleValuesForTheLevel = new int[5000];
orangeRectangleValuesForTheLevel [54] = 20;
//Create the array list with the Game_Event_Rectangles
for (int i =0; i<orangeRectangleValuesForTheLevel.length; i++) {
if (orangeRectangleValuesForTheLevel[i] >0) {
arrayList_GameEventRectangles.add(new View_Game_Event_Rectangle(getActivity(), VIEW_EVENT_RECTANGLE_SOLAR, i+1, orangeRectangleValuesForTheLevel[i]));
}
}
countDownTime();
}
private void updateScreen() {
/*
Iterate through all elements
*/
for (int currentElement =0; currentElement <arrayList_GameEventRectangles.size(); currentElement++) {
//Create view and set
if (currentTimeSlot == arrayList_GameEventRectangles.get(currentElement).getStartingTimeSlot() - 15) {
arrayList_GameEventRectangles.get(currentElement).setActive(true);
//Set the parameters and the backgorund of the view element
arrayList_GameEventRectangles.get(currentElement).setLayoutParams(new ViewGroup.LayoutParams(0, 0));
if(arrayList_GameEventRectangles.get(currentElement).getEventType().equals(VIEW_EVENT_RECTANGLE_SOLAR)) {
arrayList_GameEventRectangles.get(currentElement).setBackground(ContextCompat.getDrawable(getActivity(),R.drawable.game_event_rectangle_solar_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() - 10) {
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);
View rectangle = arrayList_GameEventRectangles.get(currentElement);
int rectangleWidth = rectangle.getWidth();
float distanceToCover_current = widthDisplay + rectangleWidth;
float distanceToCover_normalizedObject = widthDisplay + 20;
double ratioDistanceDifference = distanceToCover_current /distanceToCover_normalizedObject;
long durationForTheAnimation = (long)(numberOfMillisecondsUntilTheMiddleOfTheScreen_Level1 * ratioDistanceDifference);
arrayList_GameEventRectangles.get(currentElement).animate().setDuration(durationForTheAnimation).translationX(widthDisplay*(-1)).setInterpolator(new LinearInterpolator()).start();
}
}
}
private void countDownTime(){
cdt = new CountDownTimer(100000, DELAY_COUNT_DOWN_TIMER) {
boolean delay = true;
public void onTick(long millisUntilFinished) {
if(delay) {
delay = false;
} else {
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);
}
}
Here is the code for the RepeatListener:
package com.example.game;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
/**
* A class, that can be used as a TouchListener on any view (e.g. a Button).
* It cyclically runs a clickListener, emulating keyboard-like behaviour. First
* click is fired immediately, next one after the initialInterval, and subsequent
* ones after the normalInterval.
*
* <p>Interval is scheduled after the onClick completes, so it has to run fast.
* If it runs slow, it does not generate skipped onClicks. Can be rewritten to
* achieve this.
*/
public class RepeatListener implements View.OnTouchListener {
private Handler handler = new Handler();
private int initialInterval;
private final int normalInterval;
private final View.OnClickListener clickListener;
private View touchedView;
private Runnable handlerRunnable = new Runnable() {
@Override
public void run() {
if(touchedView.isEnabled()) {
handler.postDelayed(this, normalInterval);
clickListener.onClick(touchedView);
} else {
// if the view was disabled by the clickListener, remove the callback
handler.removeCallbacks(handlerRunnable);
touchedView.setPressed(false);
touchedView = null;
}
}
};
/**
* @param initialInterval The interval after first click event
* @param normalInterval The interval after second and subsequent click
* events
* @param clickListener The OnClickListener, that will be called
* periodically
*/
public RepeatListener(int initialInterval, int normalInterval,
View.OnClickListener clickListener) {
if (clickListener == null)
throw new IllegalArgumentException("null runnable");
if (initialInterval < 0 || normalInterval < 0)
throw new IllegalArgumentException("negative interval");
this.initialInterval = initialInterval;
this.normalInterval = normalInterval;
this.clickListener = clickListener;
}
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.removeCallbacks(handlerRunnable);
handler.postDelayed(handlerRunnable, initialInterval);
touchedView = view;
touchedView.setPressed(true);
clickListener.onClick(view);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handler.removeCallbacks(handlerRunnable);
touchedView.setPressed(false);
touchedView = null;
return true;
}
return false;
}
}
What happens is that a simple custom made view rectangle View_Game_Event_Rectangle
is animated from the right to the left.Then I have a repeat listener. When the repeat listener is pressed the background color of the custom view is changed from its original one R.drawable.game_event_rectangle_solar_1
to a new one R.drawable.game_event_rectangle_solar_2
. A handler then changes the color back after some time. The problem is, that the custom made view rectangle flickers while holding the repeatListener pressed. I tried to change the time delay for the handler but this did not solve the problem (when the delay is too high, the color remains changed for a long time). What I want is that the color of the custom made view rectangle keeps changed as long as the repeatListener button is pressed. After the repeatListener button is not pressed any more, the color should change back to its original one. The improtant aspect is that while the button is pressed, the custom made view rectangle should not flicker but just have the alternative color R.drawable.game_event_rectangle_solar_2
.
Please: Please can someone share his/her idea? I'd be quite thankful.
What I want is that the color of the custom made view rectangle keeps changed as long as the repeatListener button is pressed.
Color of you rectangle is changed back after 300ms, thats what you have coded. This might give a flicker.
Its hard to tell from the provided code what is exactly wrong. You have not provided code for RepeatListener
. I suspect that the code inside:
public void onClick(View view) {
gets executed even after first time button was touched. You should execute your on touch code when event.getAction() == MotionEvent.ACTION_DOWN
and when its event.getAction() == MotionEvent.ACTION_UP
, handle touch up action. Otherwise you will intercept also MotionEvent.ACTION_MOVE
events which can give you flicker. I assume this is not implemented in RepeatListener
.
So, when MotionEvent.ACTION_DOWN
change color to the one you want to stay for the time user touches button. Then when MotionEvent.ACTION_UP
comes, change the color back. I dont see a need for postDelayed
here.