I am trying to get a ValueAnimator to repeat once it has ended. I am using it with a SeekBar in a ListView. For some reason, the ValueAnimator will finish, trigger the onAnimationEnd() go again, but then when it reaches the end the onAnimationEnd() is never called a second time.
#Override
public View getContentView(int position, View convertView, ViewGroup parent) {
...
setupTimerBar(t);
...
}
private AnimatorListenerAdapter generateNewAnimatorListenerAdapter(final TylersContainer t) {
return new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
setupTimerBar(t);
}
};
}
private void setupTimerBar(TylersContainer t)
{
View view = t.getView();
BusTime arrivalTime = t.getBusTime();
int minutes = BusTime.differenceInMiuntes(arrivalTime, BusTime.now());
long milliseconds = minutes * 60 * 1000;
final TimerBar seekBar = (TimerBar) view.findViewById(R.id.SeekBar);
int progress = Utility.setProgress(arrivalTime, seekBar.getMax());
long duration = Utility.setAnimationDuration(progress);
seekBar.setProgress(progress);
seekBar.setAnimationDuration(duration);
seekBar.setAnimationStartDelay(milliseconds);
seekBar.setAnimatorListenerAdapter(generateNewAnimatorListenerAdapter(t));
}
The seekBar object is actually an custom object which contains a SeekBar and a ValueAnimator, here are the relevant bits:
//Constructor
public TimerBar(Context context) {
super(context);
startTime = Calendar.getInstance();
valueAnimator = ValueAnimator.ofInt(0, getMax());
//Override the update to set this object progress to the animation's value
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int animProgress = (Integer) animation.getAnimatedValue();
setProgress(animProgress);
}
});
}
//Specify the start time by passing a long, representing the delay in milliseconds
public void setAnimationStartDelay(long milliseconds){
//Set the delay (if need be) and start the counter
if(milliseconds > 0)
valueAnimator.setStartDelay(milliseconds);
valueAnimator.setIntValues(this.getProgress(), this.getMax());
valueAnimator.start();
}
//Set the duration of the animation
public void setAnimationDuration(long duration){
valueAnimator.setDuration(duration);
}
public void setAnimatorListenerAdapter(AnimatorListenerAdapter ala){
valueAnimator.addListener(ala);
}
I can't figure out why it isn't repeating more than twice.
I've tried using the Repeat attribute and setting that to INIFINITI but that didn't help either.
Edit: To be clear, what I am trying to get is an animation that repeats itself indefinitely, each time with a different duration.
I have made the error of setting the RepeatMode to infinite which did not work, this must be set as the RepeatCount:
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
In case you are using Animator, and then bind it with Animator.AnimatorListener
The function AnimatorListener.onAnimationEnd() is used in case your animation repeats for finite times, and is only called once
In case your animation is repeating, for number of times, you should be using the function AnimatorListener.onAnimationRepeat() instead, which will apply everytime your animation repeats after the end of each repeatance
From what I understand, what you need is onAnimationRepeat(), so if you just move the code that you want to be executed after each repeat from onAnimationEnd() to onAnimationRepeat(), this should fix it
Reference: http://developer.android.com/reference/android/animation/Animator.AnimatorListener.html
Related
Edit #1: Through debugging I've discovered that the bug 'disappears'. Basically I set a breakpoint and slowly go through steps of checking each multiChoiceItem and the heights of the other RecyclerView child items do not change. Does this mean it is a drawing/timing related issue?
Edit #2: Also, a new find, if I change the height of Child: 6 it changes for Child: 3 and Child: 0
I apologize for the long question. I've checked other answers regarding the same problem and none of them apply. I've tried solving this myself and just couldn't so I would love some help. If there is anything I can do to make this easier to read, please let me know and I'll get right on it!
With the way my code is written, this technically should be impossible to happen but yet here it is.
The Problem: I have an onClickListener() for a TextView within a RecyclerView item. The onClickListener() calls a multiChoiceItem AlertDialog in the container class of the RecyclerAdapter which then calls notifyDataSet(), after completed, with an addOnLayoutChangeListener() at the end which measures the height after the new RecyclerView is drawn.
Notifying that the data set ended then causes the TextView within the RecyclerView item to change to show the text of each Checked item. Then this height is measured in the addOnLayoutChangeListener() and sent to a ViewModel which measures the height of the same position item of three fragments and sets the items height to the max height so they all look the same height.
The Confusing Part: This problem only occurs for one of the three fragments AND the other effected item heights do not match the other two fragments. Which tells me that this is localized to one fragment (which has its own class)
The Code:
The code is long so I reduced it to what I think was important
The ViewHolder
class TextViewViewHolder extends RecyclerView.ViewHolder {
TextView vhTVTextView;
TextView vhTVMainTextView;
CardView vhTVCardView;
TextViewClickedListener vhTextViewClickedListener;
// Gets current position from 'onBindViewHolder'
int vhPosition = 0;
public TextViewViewHolder(View itemView, TextViewClickedListener textViewClickedListener) {
super(itemView);
this.vhTextViewClickedListener = textViewClickedListener;
this.vhTVCardView = itemView.findViewById(R.id.thoughtCard);
this.vhTVTextView = itemView.findViewById(R.id.thoughtNumber);
this.vhTVMainTextView = itemView.findViewById(R.id.textEntry);
/*
When the main TextView is clicked, it calls a function in the container
'FragTextView' which pops up an AlertDialog. It was chosen to do it in the
container instead of here because the Adapter is so adapt the lists data to the view
and the container is what dictates what the lists data actually is.
*/
vhTVMainTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(vhTextViewClickedListener != null) {
vhTextViewClickedListener.onTextViewClicked(vhPosition);
}
}
});
}
}
onBindViewHolder
#Override
public int getItemViewType(int position) {
/*
If mThoughtEntries is not null, then that means we can find the ViewType we are working
with inside of it. Otherwise, we are mDistortions and we must be working on TYPE_TEXTVIEW
*/
if(mThoughtEntries != null) return mThoughtEntries.get(position).getViewType();
else return Constants.TYPE_TEXTVIEW;
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
int adapterPosition = holder.getAdapterPosition();
switch (holder.getItemViewType()) {
case Constants.TYPE_EDITTEXT:
EditTextViewHolder editTextViewHolder = (EditTextViewHolder)holder;
// update MyCustomEditTextListener every time we bind a new item
// so that it knows what item in mDataset to update
editTextViewHolder.mMyCustomEditTextListener.setTWPosition(holder.getAdapterPosition());
//Displaying list item to its correct position
editTextViewHolder.vhETTextView.setText(String.valueOf(adapterPosition + 1));
editTextViewHolder.vhETEditText.setText(mThoughtEntries.get(adapterPosition).getThought());
break;
case Constants.TYPE_TEXTVIEW:
TextViewViewHolder textViewViewHolder = (TextViewViewHolder)holder;
// Send current position to viewHolder so when the text listener is called, it knows
// exactly which position of the Distortions list to change
textViewViewHolder.vhPosition = adapterPosition;
//Displaying list item to its correct position
textViewViewHolder.vhTVTextView.setText(String.valueOf(adapterPosition + 1));
textViewViewHolder.vhTVMainTextView.setText(distortionsToString(mDistortions.get(adapterPosition)));
break;
}
}
AlertDialog in Parent
#Override
public void onTextViewClicked(int position) {
//pass the 'context' here
AlertDialog.Builder alertDialog = new AlertDialog.Builder(getContext());
final int recyclerPosition = position;
/*
Turning the distortions into a list of strings and an array of what should, or should
not, be checked.
*/
final String[] distortionStrings = distortionNameToStringArray(mDistortions.get(position));
final boolean[] checkedDistortions = distortionCheckToBooleanArray(mDistortions.get(position));
alertDialog.setMultiChoiceItems(distortionStrings, checkedDistortions,
new DialogInterface.OnMultiChoiceClickListener() {
#Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
if (isChecked) {
// If the user checked the item, add it to the selected items
mDistortions.get(recyclerPosition).get(which).setChecked(true);
} else {
// Else, if the item is already in the array, remove it
mDistortions.get(recyclerPosition).get(which).setChecked(false);
}
/*
Because the RecyclerView takes a while to draw, if we call the below function
as we normally we would, it would appear to have no effect because it would
be automatically overwritten when the RecyclerView is drawn. So we call this
onLayout change listener to wait til the view is drawn and then we call
the function
*/
mRecyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
mRecyclerView.removeOnLayoutChangeListener(this);
// Send new height to the ViewModel
if(mLayoutManager.findViewByPosition(recyclerPosition) != null) {
// Get view of item measuring
View recyclerChild = mLayoutManager.findViewByPosition(recyclerPosition);
// Get LinearLayout from view
LinearLayout linearLayout = recyclerChild.findViewById(R.id.horizontalLayout);
// This is called to find out how big a view should be. The constraints are to check
// measurement when it is set to 'wrap_content'.
linearLayout.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
// Get height of the specified view
int height = linearLayout.getMeasuredHeight();
// Send to child abstracted class which then calls function from 'SharedEntryFragments'
setViewModelHeight(height, recyclerPosition);
}
}
});
mAdapter.notifyDataSetChanged();
}
});
alertDialog.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// DO SOMETHING HERE
dialog.cancel();
}
});
AlertDialog dialog = alertDialog.create();
dialog.show();
}
The function that makes all the fragment item heights equal
I know this part of the code doesn't affect it because where the views that heights are changed are skipped by if(positionalHeight.get(i) != 0) {} So technically...they should never change!
/*
This is the listener that will set all the RecyclerViews childrens heights. It
listens to getTallestLiveHeight() inside of 'SharedEntryFragments.java' and when
a change occurs, this is called
*/
if(getActivity() != null) {
// The container holds the ViewModel so this must make sure getActivity() is not null
mViewModel = ViewModelProviders.of(getActivity()).get(SharedEntryFragments.class);
/*
Creates the observer which updates the UI. The observer takes the
PositionalHeight class as an input. This class keeps track of which index
of the RecyclerView to change and what height it will be changed to.
*/
final Observer<List<Integer>> maxHeight = new Observer<List<Integer>>() {
#Override
public void onChanged(#Nullable final List<Integer> positionalHeight) {
if (positionalHeight != null) {
// Get the index that we are going to change and its height
//int position = positionalHeight.getPosition();
//int height = positionalHeight.getHeight();
/*
We're going to run through each child of mRecyclerView and change
its height accordingly
*/
int listSize = positionalHeight.size();
for(int i = 0; i < listSize; i++) {
// If height reads zero then skip because it will make our view disappear
if(positionalHeight.get(i) != 0) {
// This is the child item that we will be changing
View recyclerChild = mLayoutManager.findViewByPosition(i);
// Ensure that the child exists before continuing
if (recyclerChild != null) {
// We will be changing the CardView's height
// TODO might have to add a check to detect which viewholder
CardView cardView = recyclerChild.findViewById(R.id.thoughtCard);
// Get the LayoutParams first to ensure everything stays the same
ViewGroup.LayoutParams lparams = cardView.getLayoutParams();
// Get and set height
lparams.height = positionalHeight.get(i);
cardView.setLayoutParams(lparams);
}
}
}
}
}
};
mViewModel.getTallestLiveHeight().observe(this, maxHeight);
}
}
I wish I could provide a better answer for other people but this is what I discovered:
For some reason when I call mAdapter.notifyDataSetChanged(); in the AlertDialog function, every third item in the RecyclerView changed to the equaled height. I decided to change it to mAdapter.notifyItemChanged(recyclerPosition); to save on memory and, coincidentally, the bug has disappeared.
If someone could explain why, I will set that as the accepted answer but as of now, this satisfies the question so I will keep it as an answer.
I'm an Android newbie developer and having an issue with a tiny Android app I'm developing which I suspect is related to timing of dynamic view creation. It's a little scorekeeper app and it has dynamically generated player buttons and text fields, defined in a Player class. Here's an excerpt of how I'm doing this:
public Player(String name, int score, int number, boolean enabled, RelativeLayout mainScreen, List<Button> presetButtonList, Editor editor, Context context) {
super();
this.name = name;
this.score = score;
this.number = number;
this.enabled = enabled;
this.editor = editor;
// Get the lowest child on the mainScreen
Integer count = mainScreen.getChildCount(), lowestChildId = null;
Float lowestChildBottom = null;
for (int i = 0; i < count; i++) {
View child = mainScreen.getChildAt(i);
if ((lowestChildId == null || child.getY() > lowestChildBottom) &&
!presetButtonList.contains(child)) {
lowestChildId = child.getId();
lowestChildBottom = child.getY();
}
}
playerNameText = (EditText) setTextViewDefaults(new EditText(context), name);
playerNameText.setSingleLine();
// playerNameText.setMaxWidth(mainScreen.getWidth());
// playerNameText.setEllipsize(TextUtils.TruncateAt.END); //TODO: Prevent names which are too long for screen
playerNameText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable changedText) {
setName(changedText.toString());
updateScreen();
}
public void beforeTextChanged(CharSequence changedText, int start, int count, int after) {}
public void onTextChanged(CharSequence changedText, int start, int before, int count) {}
});
RLParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
if (lowestChildId != null) {
RLParams.addRule(RelativeLayout.BELOW, lowestChildId);
} else {
RLParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
RLParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
}
RLParams.setMargins(0, 10, 0, 10);
mainScreen.addView(playerNameText, RLParams);
the class further defines buttons and etc in a similar fashion, aligning tops with the name text view. I call this when a user clicks a button to add a player, and it works fine, displaying each player below the first on down the screen. The problem comes in when I'm loading a bunch of saved players at the start. Here's where i load the players:
public void loadData() {
RelativeLayout mainScreen = (RelativeLayout)findViewById(R.id.relativeLayout);
playerCount = savedData.getInt("playerCount", playerCount);
Player player;
String name;
playerList.clear();
for (int i=1; i<=playerCount; i++) {
name = savedData.getString("name" + i, null);
if (name != null) {
Log.v("name", name);
player = new Player(name, savedData.getInt("score" + i, 0), i, savedData.getBoolean("enabled" + i, false), mainScreen, presetButtonList, editor, this);
playerList.add(player);
player.updateScreen();
}
}
updateScreen();
}
Finally, I call the loadData() method when the app starts, here:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
savedData = this.getPreferences(Context.MODE_PRIVATE);
editor = savedData.edit();
presetButtonList.add((Button)findViewById(R.id.newGame));
presetButtonList.add((Button)findViewById(R.id.addPlayer));
presetButtonList.add((Button)findViewById(R.id.removePlayer));
loadData();
}
The result? When there are more than two players to load, all the players get loaded to the same spot, on top of Player 2.
I suspect somehow that the players are all being generated at the same time and thus all believing that the lowest player view is Player 1, and not checking each other. I've tried triggering the load later than onCreate() and it still happens. I also tried adding a 3 second sleep within the for loop of loadData() after each player loads to see if that helped, but no luck.
Am I making bad assumptions? What am I doing wrong and how might I fix it?
I suspect what is happening here is that you are attempting to position views that don't have IDs (it looks like you don't set them anywhere in your code). Dynamically created views don't automatically have IDs, so a call to getId() will return NO_ID (See documentation). If you want to use an ID you will have to set it yourself using View.setId().
It was a timing issue after all. I needed to wait for the main screen to load before I could add views to it. Unfortunately, it would never load until all activity on the UI thread was finished.
Thus, I needed threading. I ended up putting loadData() in a thread, like this:
new Thread(new loadDataAsync(this)).start();
class loadDataAsync implements Runnable {
MainActivity mainActivity;
public loadDataAsync(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
#Override
public void run() {
try {
Thread.sleep(700);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
loadData(mainActivity);
}
}
Then I created the players back on the UI thread after the initial sleep to give time for the UI to load.
runOnUiThread(new Runnable() {
Player player;
RelativeLayout mainScreen = (RelativeLayout)findViewById(R.id.relativeLayout);
#Override
public void run() {
player = new Player(savedData.getString("name" + playerNum, null), savedData.getInt("score" + playerNum, 0), playerNum, savedData.getBoolean("enabled" + playerNum, false), mainScreen, presetButtonList, editor, mainActivity);
playerList.add(player);
player.updateScreen();
mainScreen.invalidate();
}
});
Not the most elegant solution, but it works for now.
I am new to android and I need help.
I want my image which is falling from the top of screen to get invisible when I click a button on the keyboard which has the same tag as the image. Although I have implemented the above points a problem arises when I want to show the next animation again immediately after the previous image gets invisible. This is my code for animation.
public void startAnimation(final ImageView aniView)
{
animator= ValueAnimator.ofFloat(0 ,.85f);
animator.setDuration(Constants.ANIM_DURATION);
animator.setInterpolator(null);
//generation of random values
Random rand = new Random();
index = rand.nextInt((int) metrics.widthPixels);
//for debugging
i = Integer.toString(index);
Log.e(" index is :", i);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
{
#Override
public void onAnimationUpdate(ValueAnimator animation)
{
float value = ((Float) (animation.getAnimatedValue())).floatValue();
aniView.setTranslationX(index);
aniView.setTranslationY((height+ (50*mScale))*value);
//aniView.setTranslationY(height);
}
});
animator.start();
}
//keyboard methods goes here
#Override
public void onClick(View v)
{
gettext(v);
}
private void gettext(View v)
{
try
{
String b = "";
b = (String) v.getTag();
String img_val = map.get(b);
int imgid = (Integer) imageView.getTag();
Log.e("img values=:", img_val+" "+imgid);
if(img_val.equals(ALPHABETS[imgid]))
{
imageView.setVisibility(View.INVISIBLE);
animator.start();
//trying to start the animation again
}
I am using the animation.addupdateListener to call the animation again and again but it only calls when the .ofFloat finishes on the value. When I click on the appropriate button the animation gets invisible but the next animation starts again after the same time. I want to start it immediately.
Basically calling start() again won't work. you need to "reset" the animation somehow, did you try calling end() before ?
animator.end();
animator.start();
I have got a TimePickerDialog working to set time which is set to a TextView in order to display it. Now, I need help to set that TimePicker (inside the TimePickerDialog) minutues interval to 15 minutes. I have seen there is a post with 15 minutes interval issue related to TimePicker, but I don't know how to apply it to the TimePickerDialog because I don't know how to use the TimePicker that it is created inside the TimePickerDialog. I am new to Android and completely lost in this matter. Thanks in advance.
Using a combination of this from #Rizwan and this other thread, I came up with a combined solution that allows arbitrary minute increments in a TimePickerDialog. The main issue is that most of the functionality is hidden by the android TimePickerDialog and TimePicker classes and it doesn't appear to allow
Extend TimePickerDialog to allow us easier access
Use reflection to reach inside the display and access the required bits (see below)
rewire the minute 'NumberPicker' to display our values
rewire the TimePicker to receive and return values form the NumberPicker honoring our custom increment.
block onStop() so that it doesn't reset the value on close.
Warning
The main issue with reaching inside the UI is that elements are referenced by ids which are likely to change, and even the name of the id is not guaranteed to be the same forever. Having said that, this is working, stable solution and likely to work for the foreseeable future. In my opinion the empty catch block should warn that the UI has changed and should fall back to the default (increment 1 minute) behaviour.
Solution
private class DurationTimePickDialog extends TimePickerDialog
{
final OnTimeSetListener mCallback;
TimePicker mTimePicker;
final int increment;
public DurationTimePickDialog(Context context, OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView, int increment)
{
super(context, callBack, hourOfDay, minute/increment, is24HourView);
this.mCallback = callBack;
this.increment = increment;
}
#Override
public void onClick(DialogInterface dialog, int which) {
if (mCallback != null && mTimePicker!=null) {
mTimePicker.clearFocus();
mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
mTimePicker.getCurrentMinute()*increment);
}
}
#Override
protected void onStop()
{
// override and do nothing
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
try
{
Class<?> rClass = Class.forName("com.android.internal.R$id");
Field timePicker = rClass.getField("timePicker");
this.mTimePicker = (TimePicker)findViewById(timePicker.getInt(null));
Field m = rClass.getField("minute");
NumberPicker mMinuteSpinner = (NumberPicker)mTimePicker.findViewById(m.getInt(null));
mMinuteSpinner.setMinValue(0);
mMinuteSpinner.setMaxValue((60/increment)-1);
List<String> displayedValues = new ArrayList<String>();
for(int i=0;i<60;i+=increment)
{
displayedValues.add(String.format("%02d", i));
}
mMinuteSpinner.setDisplayedValues(displayedValues.toArray(new String[0]));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
constructor accepts the increment value and retains some other references. Note that this omits error checking and we'd prefer 60%increment==0
onCreate uses the name of the UI fields and reflection to discover the current location. Again this omits error checking and should be 'fail-safe' ie revert to default behaviour if something goes wrong.
onClick overridden to return the correct minute value to the callback listener
onStop overridden to prevent the (incorrect) index value being returned a second time, when the dialog closes. Go on, try it yourself.
Most of this comes from digging into the TimePickerDialog source.
UPDATE FOR ANDROID 11+
The reflection used in the example above was marked as greylist-max-q, meaning it would no longer be possible after Android 10 (API 29 - Q). In the short term (until August 2021) it is possible to drop the target version back to API 29 while still building against Android 11 (API 30 - R) but after this date the Play Store will no longer accept these builds and will require API 30 as the build target. This eventual failure was anticipated and noted in the warning above.
well thats fine if you used time-picker instead of time-picker-dialog. But there is a solution for this actually.. here is what I used to meet the same requirement.. I used CustomTimePickerDialog. Every thing will be same, only TimePickerDialog will change to CustomTimePickerDialog in the code.
CustomTimePickerDialog timePickerDialog = new CustomTimePickerDialog(myActivity.this, timeSetListener,
Calendar.getInstance().get(Calendar.HOUR),
CustomTimePickerDialog.getRoundedMinute(Calendar.getInstance().get(Calendar.MINUTE) + CustomTimePickerDialog.TIME_PICKER_INTERVAL),
false
);
timePickerDialog.setTitle("2. Select Time");
timePickerDialog.show();
Here is my CustomTimePickerDialog class... Just use this class in your project and change TimePickerDialog to CustomTimePickerDialog..
public class CustomTimePickerDialog extends TimePickerDialog{
public static final int TIME_PICKER_INTERVAL=10;
private boolean mIgnoreEvent=false;
public CustomTimePickerDialog(Context context, OnTimeSetListener callBack, int hourOfDay, int minute,
boolean is24HourView) {
super(context, callBack, hourOfDay, minute, is24HourView);
}
/*
* (non-Javadoc)
* #see android.app.TimePickerDialog#onTimeChanged(android.widget.TimePicker, int, int)
* Implements Time Change Interval
*/
#Override
public void onTimeChanged(TimePicker timePicker, int hourOfDay, int minute) {
super.onTimeChanged(timePicker, hourOfDay, minute);
this.setTitle("2. Select Time");
if (!mIgnoreEvent){
minute = getRoundedMinute(minute);
mIgnoreEvent=true;
timePicker.setCurrentMinute(minute);
mIgnoreEvent=false;
}
}
public static int getRoundedMinute(int minute){
if(minute % TIME_PICKER_INTERVAL != 0){
int minuteFloor = minute - (minute % TIME_PICKER_INTERVAL);
minute = minuteFloor + (minute == minuteFloor + 1 ? TIME_PICKER_INTERVAL : 0);
if (minute == 60) minute=0;
}
return minute;
}
}
After using this CustomTimePickerDialog class, All you will need to do is use CustomTimePickerDialog instead of TimePickerDialog in your code to access/override default functions of TimePickerDialog class. In the simple way, I mean your timeSetListener will be as following after this...
private CustomTimePickerDialog.OnTimeSetListener timeSetListener = new CustomTimePickerDialog.OnTimeSetListener() {
#Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
}
}// using CustomTimePickerDialog
You can use a regular AlertDialog and use setView to include a custom TimePicker view.
/**
* Set TimePicker interval by adding a custom minutes list
* TIME_PICKER_INTERVAL = Enter your Minutes;
* #param timePicker
*/
private void setTimePickerInterval(TimePicker timePicker) {
try {
int TIME_PICKER_INTERVAL = 10;
NumberPicker minutePicker = (NumberPicker) timePicker.findViewById(Resources.getSystem().getIdentifier(
"minute", "id", "android"));
minutePicker.setMinValue(0);
minutePicker.setMaxValue((60 / TIME_PICKER_INTERVAL) - 1);
List<String> displayedValues = new ArrayList<String>();
for (int i = 0; i < 60; i += TIME_PICKER_INTERVAL) {
displayedValues.add(String.format("%02d", i));
}
minutePicker.setDisplayedValues(displayedValues.toArray(new String[0]));
} catch (Exception e) {
Log.e(TAG, "Exception: " + e);
}
}
This is the first time I am posting something on StackOverflow and that it only because I felt like this is one of those times I was not able to find what I needed from the vast Internet!
I am currently making an Android Application that is a game where I have an ball that is an imageview and each time I click on the ball, it moves to a random location with this method I've built:
private void changePos() {
RelativeLayout gameLayout = findViewById(R.id.gameFrame);
int frameHeight = gameLayout.getHeight();
int frameWidth = gameLayout.getWidth();
Random ballLocationY = new Random();
int ballY = ballLocationY.nextInt(frameHeight);
Random ballLocationX = new Random();
int ballX = ballLocationX.nextInt(frameWidth);
gameLayout.setY(ballY);
gameLayout.setX(ballX);
}
So what I am struggling with is to reduce the timer with each click.
What I am trying to do is to reduce the timer with each click on the ball by a certain percentage.
Basically each time the user clicks on the ball, the timer starts off from 0 and counting upwards in seconds and milliseconds, and at 3 second mark the game is over.
This is the approach I am playing around with:
Runnable updateThreadTimer = new Runnable() {
#Override
public void run() {
timeInMilliseconds = System.Clock.uptimeMillis() - startTime();
updateTime = timeSwapBuff + timeInMilliseconds;
int secs = (int) (updateTime / 1000);
countDownText.setText("" + String.format("%2d", secs) + ":"
+ String.format("%3d", timeInMilliseconds));
mHandler.postDelayed(this, 0);
if (secs == 3 && timeInMilliseconds >= 3000) {
timeSwapBuff += timeInMilliseconds;
mHandler.removeCallbacks(updateThreadTimer);
}
}
}
Variables necessary:
long startTime = 0L, timeInMilliseconds = 0L, timeSwapBuff = 0L, updateTime = 0L;
Handler mHandler = new Handler();
And then I have this in the Overridden onCreate method:
ball.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
changePos();
startTime = SystemClock.uptimeMillis();
mHandler.postDelayed(updateThreadTimer, 0);
}
return true;
}
}
So what I get is a timer that gets reset to 0 with each click on the button and starts again. And the timer displays in seconds and milliseconds: 0:0000, which is what I wanted, but I wanna reduce a certain percentage with each click and update the time.
So let's say the counter starts with 3 seconds limit, after clicking on the ball couple of times, the timer should eventually come down and the game should fail if the timer goes above that time.
Obviously I have simplified the code quite a lot to get straight to the point.
I was wondering if anyone has any suggestions on how I could approach the problem I have.
I also want to apologize if the question is not asked properly, being the first time posting, leave suggestions if theres any hatin' going on!
And I also want to mention it one more time, I've looked around for a solution like this, and the code I have put together is from lots of different sources, so I feel like I've done my research, and not being able to find something, hence I am turning to StackOverflow!
So I guess I am going to answer my own question as I have solved it now and also in hope that this may solve problems for some.
I redid some parts of the code and scratched the Runnable way, instead I took an approach that uses CountDownTimer class.
private long currentTime;
private long maxTimer = 3000;
private double fivePercent = .05;
private CountDownTimer myTimer = null;
protected void onCreate(Bundle savedInstanceState) {
// Some code...
// Assign 3000 to currentTime
currentTime = maxTimer;
ball.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
if (myTimer != null) {
// Cancel timer
myTimer.cancel();
// Calculating 5 % from maxTimer
long a = (long) (maxTimer * fivePercent);
maxTimer -= a;
// Storing new maxTimer in currentTime
currentTime = maxTimer;
}
myTimer = new CountDownTimer(currentTime, 1) {
#Override
public void onTick(long millisUntilFinished) {
countDownText.setText("Time: " + String.valueOf(millisUntilFinished));
currentTime = millisUntilFinished;
}
#Override
public void onFinish() {
Intent intent = new Intent(getApplicationContext(), ResultsActivity.class);
GameActivity.this.startActivity(intent);
}
}.start();
changePos();
scoreLabel.setText("Score: " + score++ + "");
}
return true;
}
});
}
I have simplified the code for demonstration purpose, but basically what I did was to create a CountDownTimer, and whenever I click on the ball, the CountDownTimer will get cancelled, and invoke a new one, before creating a new CountDownTimer it will calculate off 5 % and put the result into the new CountDownTimer.