I have an activity which has a ProgressBar. This bar is used to show the elapsed time on the game level.
I'm updating this bar and a TextView with a CountdownTimer, called every 100ms. The problem is that every time I call setProgress, it seems to be causing an invalidate() that makes my whole UI to be redrawn. If I delete the line where the ProgressBar get updated everything works fine, even setText for the TextView that show the time left.
This is a problem for me because I also have a custom view which needs to be redrawn only when I need it, or at least a few times but not constantly because it'll affect performance.
This is a piece of my code:
private CountDownTimer timer;
private long ttime;
private long ctime;
private ProgressBar bar;
private TextView clock;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
startTimer(500);
...
}
#Override
protected void onResume() {
super.onResume();
if(!checkFlags()){
startTimer(500);
}
}
#Override
protected void onPause() {
super.onPause();
if(!checkFlags()){
timer.cancel();
}
}
private void startTimer(long delay){
timer = new CountDownTimer(ctime,100){
public void onTick(long millisUntilFinished){
ctime = millisUntilFinished;
clock.setText(formatTime(millisUntilFinished));
bar.setProgress((int) (1000 - ((ctime * 1000)/ttime)));
}
public void onFinish(){
clock.setText("00:00");
gameOver(false);
}
};
if(delay > 0){
Handler handler = new Handler();
handler.postDelayed(new Runnable(){
public void run(){
timer.start();
}
},delay);
}else{
timer.start();
}
}
How could I prevent the ProgressBar to cause this onDraw calls for every element of the UI?
According to 'sources/android-18/android/widget/ProgressBar.java' in Android SDK the call of setProgress() will result in the call of invalidate().
private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
boolean callBackToApp) {
float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
final Drawable d = mCurrentDrawable;
if (d != null) {
Drawable progressDrawable = null;
if (d instanceof LayerDrawable) {
progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
if (progressDrawable != null && canResolveLayoutDirection()) {
progressDrawable.setLayoutDirection(getLayoutDirection());
}
}
final int level = (int) (scale * MAX_LEVEL);
(progressDrawable != null ? progressDrawable : d).setLevel(level);
} else {
>>> invalidate();
}
if (callBackToApp && id == R.id.progress) {
onProgressRefresh(scale, fromUser);
}
}
Try to use setProgressDrawable(). It seems that invalidate() is not called in that case.
Related
I have a custom view that has a blinking cursor. I make the blinking cursor using a Handler and posting a Runnable to it after a 500 milisecond delay.
When the activity that the view is in, I want to stop the blinking by removing the callbacks on the handler. However, I've noticed that when I switch to another app, the handler/runnable keep going, ie, the log says it is still blinking.
If I had control of the view I would just do something like this
#Override
protected void onPause() {
handler.removeCallbacks(runnable);
super.onPause();
}
But my custom view will be part of a library and so I don't have control over the Activities that other developers use my custom view in.
I tried onFocusChanged, onScreenStateChanged, and onDetachedFromWindow but none of these work for when the user switches to another app.
Here is my code. I simplified it by removing anything not pertinent to the problem.
public class MyCustomView extends View {
static final int BLINK = 500;
private Handler mBlinkHandler;
private void init() {
// ...
mBlinkHandler = new Handler();
mTextStorage.setOnChangeListener(new MongolTextStorage.OnChangeListener() {
#Override
public void onTextChanged(/*...*/) {
// ...
startBlinking();
}
});
}
Runnable mBlink = new Runnable() {
#Override
public void run() {
mBlinkHandler.removeCallbacks(mBlink);
if (shouldBlink()) {
// ...
Log.i("TAG", "Still blinking...");
mBlinkHandler.postDelayed(mBlink, BLINK);
}
}
};
private boolean shouldBlink() {
if (!mCursorVisible || !isFocused()) return false;
final int start = getSelectionStart();
if (start < 0) return false;
final int end = getSelectionEnd();
if (end < 0) return false;
return start == end;
}
void startBlinking() {
mBlink.run();
}
void stopBlinking() {
mBlinkHandler.removeCallbacks(mBlink);
}
#Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (focused) {
startBlinking();
} else {
stopBlinking();
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
#Override
public void onScreenStateChanged(int screenState) {
switch (screenState) {
case View.SCREEN_STATE_ON:
startBlinking();
break;
case View.SCREEN_STATE_OFF:
stopBlinking();
break;
}
}
public void onAttachedToWindow() {
super.onAttachedToWindow();
startBlinking();
}
#Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopBlinking();
}
}
I guess you are starting the thread separately using thread.run(), instead just make a method and call it recursively Something like this:
public void blink(){
mBlinkHandler.postDelayed(mBlink, BLINK);
}
And in the runnable:
Runnable mBlink = new Runnable() {
#Override
public void run() {
mBlinkHandler.removeCallbacks(mBlink);
if (shouldBlink()) {
// ...
Log.i("TAG", "Still blinking...");
blink();
}
}
};
As you are directly starting the thread using run method. So it won't stop by removing callbacks.
Hope this helps.
I solved the problem by following #pskink's advice in the comments and adapted the code from Android 1.6. This may be an old version of Android but the blinking cursor part works well for my purposes. Overriding onWindowFocusChanged was the key.
My full code is on GitHub. Here are the pertinent parts:
public class MyCustomView extends View {
private boolean mCursorVisible = true;
private Blink mBlink;
private long mShowCursor; // cursor blink timing based on system clock
static final int BLINK = 500;
#Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
mShowCursor = SystemClock.uptimeMillis();
if (focused) {
makeBlink();
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
#Override
protected void onDraw(Canvas canvas) {
int start = getSelectionStart();
int end = getSelectionEnd();
// draw the blinking cursor on top
if (start == end && blinkShouldBeOn()) {
canvas.drawRect(getCursorPath(start), mCursorPaint);
}
}
private boolean blinkShouldBeOn() {
if (!mCursorVisible || !isFocused()) return false;
return (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK;
}
private void makeBlink() {
if (!mCursorVisible) {
if (mBlink != null) {
mBlink.removeCallbacks(mBlink);
}
return;
}
if (mBlink == null)
mBlink = new Blink(this);
mBlink.removeCallbacks(mBlink);
mBlink.postAtTime(mBlink, mShowCursor + BLINK);
}
public void setCursorVisible(boolean visible) {
mCursorVisible = visible;
invalidateCursorPath();
if (visible) {
makeBlink();
} else if (mBlink != null) {
mBlink.removeCallbacks(mBlink);
}
}
#Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
if (mBlink != null) {
mBlink.uncancel();
if (isFocused()) {
mShowCursor = SystemClock.uptimeMillis();
makeBlink();
}
}
} else {
if (mBlink != null) {
mBlink.cancel();
}
hideSystemKeyboard();
}
}
private static class Blink extends Handler implements Runnable {
private WeakReference<MongolEditText> mView;
private boolean mCancelled;
Blink(MongolEditText v) {
mView = new WeakReference<>(v);
}
public void run() {
if (mCancelled) {
return;
}
removeCallbacks(Blink.this);
MongolEditText met = mView.get();
if (met != null && met.isFocused()) {
int st = met.getSelectionStart();
int en = met.getSelectionEnd();
if (st == en && st >= 0 && en >= 0) {
if (met.mLayout != null) {
met.invalidateCursorPath();
}
postAtTime(this, SystemClock.uptimeMillis() + BLINK);
}
}
}
void cancel() {
if (!mCancelled) {
removeCallbacks(Blink.this);
mCancelled = true;
}
}
void uncancel() {
mCancelled = false;
}
}
}
I am trying to create a small animation which changes smoothly the background color. My problem is that it only shows the last value (100, that means it directly goes to a red background). I don't know why my created while-loop doesn't actualize every value (so that it would show a smoothly color animation)
New code (which almost works, but Idk how to stop the animation)
imageButton_info.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
final Handler handler = new Handler();
Runnable ChangeBackgroundRunnable = new Runnable() {
#Override
public void run() {
number++;
float[] hsvColor = {0, 1, 1};
hsvColor[0] = 360f * number / 100;
color.setBackgroundColor(Color.HSVToColor(hsvColor));
handler.postDelayed(this, 80);
if (number >=100)
number = 1;
}
};
number = 0;
handler.removeCallbacks(ChangeBackgroundRunnable);
handler.postDelayed(ChangeBackgroundRunnable, 0);
}
});
Code:
public void onClick(View v){
try {
while (number<=100) {
number=number+1;
float[] hsvColor = {0, 1, 1};
hsvColor[0] = 360f * number / 100;
color.setBackgroundColor(Color.HSVToColor(hsvColor));
Thread.sleep(10);
}
}catch(Exception e){
//New exception
Log.e("Camera Error!",e.getMessage());
}
Thank you for your answer in advance...
When you change something in the UI, it doesn't happen immediately. Instead, it posts a message to the Looper on the UI thread. When control returns to the looper (when you're done with whatever function the framework called), it will process all the messages on the Looper, until it eventually processes the redraw request. Then it will draw. So if you're looping in onClick, you will not get any updates to the screen. If you want something to happen in 10ms, post a delayed message to a Handler and update the UI in that thread.
Side note: NEVER EVER sleep on the UI thread. The reason is that no input or draw commands can be processed if you're not returning control to the Looper. So your app becomes unresponsive. If you do it long enough, it can even cause the framework to kill your app for being unresponsive.
A better way to do this would be to use an Android animation. I stole the code for this from here
int colorFrom = getResources().getColor(R.color.red);
int colorTo = getResources().getColor(R.color.blue);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.setDuration(250); // milliseconds
colorAnimation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
textView.setBackgroundColor((int) animator.getAnimatedValue());
}
});
colorAnimation.start();
Wrapped the answers posted by #gabe-sechan and #jesse-buss
ValueAnimator support from devices SDK above HONEYCOMB. So below that SDK level we'll use #gabe-sechan suggestion. Check the below code.
private void executeBackgroundChange() {
// Handler and runnable to run the animation in devices sdk below honeycomb.
mHandler = new Handler();
mChangeBackgroundRunnable = new Runnable() {
#Override
public void run() {
number++;
float[] hsvColor = {0, 1, 1};
hsvColor[0] = 360f * number / 100;
mContainer.setBackgroundColor(Color.HSVToColor(hsvColor));
mHandler.postDelayed(this, 500);
if (number == 100)
number = 0;
}
};
number = 0;
mHandler.removeCallbacks(mChangeBackgroundRunnable);
mHandler.postDelayed(mChangeBackgroundRunnable, 0);
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void executeBackgroundChangeUsingValueAnimator() {
colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), getResources().getColor(R.color.red), getResources().getColor(R.color.blue));
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(final ValueAnimator animator) {
mContainer.setBackgroundColor((Integer) animator.getAnimatedValue());
}
});
colorAnimation.setRepeatCount(ValueAnimator.INFINITE);
colorAnimation.setDuration(10 * 1000);
colorAnimation.start();
}
Add the below method and to stop the animation on click of something call the below method.
private void stopBackgroundChangeAnimation() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
if (colorAnimation != null && colorAnimation.isRunning())
colorAnimation.end();
} else {
if (mHandler != null && mChangeBackgroundRunnable != null)
mHandler.removeCallbacks(mChangeBackgroundRunnable);
}
}
Check the github project for reference.
Try to use runOnUiThread
public void onCreate(Bundle savedInstanceState) {
res = getResources();
super.onCreate(savedInstanceState);
setContentView(R.layout.xyz);//**Works**/
handler.postDelayed(runnable, 10);
}
private Runnable runnable = new Runnable() {
public void run() {
runOnUiThread(new Runnable() {
public void run()
{
try {
while (number<=100) {
number=number+1;
float[] hsvColor = {0, 1, 1};
hsvColor[0] = 360f * number / 100;
color.setBackgroundColor(Color.HSVToColor(hsvColor));
}
}catch(Exception e){
//New exception
}
}
});
}
}
I want to make an activity where user at the beginning will see first statement then after 2 sec, there'll be second statement and at last there show up timer with progress drawn on canvas in onDraw method.
Timer should only last 5 seconds, but when I add those two 2 sec breaks, user will see only last second (5-2-2). I tried to change code where progress is calculated, but I just make things worse.
I cutted out variable declaration and all content about shapes being drawing. Maybe i should declarate in diffrent moment startTime and currentTime.
public class CircleTimer extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final CircularCountdown view = new CircularCountdown(this);
setContentView(view);
AsyncTask<Void, Boolean, Void> task = new AsyncTask<Void, Boolean, Void>() {
#Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(2000);
publishProgress(true);
Thread.sleep(2000);
} catch (InterruptedException e) {
}
return null;
}
protected void onProgressUpdate(Boolean... progress) {
view.setSecondDrawStep(progress[0]);
view.invalidate();
}
#Override
protected void onPostExecute(Void result) {
view.setThirdDrawStep(true);
view.invalidate();
}
};
task.execute((Void[])null);
}
private class CircularCountdown extends View {
public CircularCountdown(Context context) {
super(context);
maxTime = 5000;
startTime = System.currentTimeMillis();
currentTime = startTime;
viewHandler = new Handler();
updateView = new Runnable(){
#Override
public void run(){
currentTime = System.currentTimeMillis();
progressMillisecond = Math.abs(startTime - currentTime) % maxTime;
progress = (double) (progressMillisecond) / maxTime;
CircularCountdown.this.invalidate();
viewHandler.postDelayed(updateView, 1000/60);
}
};
viewHandler.post(updateView);
}
boolean secondDrawStep = false;
boolean thirdDrawStep = false;
public void setSecondDrawStep(boolean flag) {
secondDrawStep = flag;
}
public void setThirdDrawStep(boolean flag) {
thirdDrawStep = flag;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("FIRST statement",
centerWidthCenter,
centerHeightCenter + textOffset,
nameTextPaint);
if (secondDrawStep) {
canvas.drawColor(Color.WHITE);
canvas.drawText("SECOND statement",
centerWidthCenter,
centerHeightCenter + textOffset,
nameTextPaint);
}
if (thirdDrawStep) {
canvas.drawColor(Color.WHITE);
canvas.drawArc(circleBounds, -90, (float) (progress * 360), false, progressPaint);
canvas.drawText((int) Math.abs(5 - (progressMillisecond / 1000)) + "s",
centerWidth,
centerHeight + textOffset,
textPaint);
}
}
}
}
I am using a ProgressBar in my application which I update in onProgressUpdate of an AsyncTask. So far so good.
What I want to do is to animate the progress update, so that it does not just "jump" to the value but smoothly moves to it.
I tried doing so running the following code:
this.runOnUiThread(new Runnable() {
#Override
public void run() {
while (progressBar.getProgress() < progress) {
progressBar.incrementProgressBy(1);
progressBar.invalidate();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
The problem is that the progress bar does not update its state until it finished its final value (progress variable). All states in between are not displayed on the screen. Calling progressBar.invalidate() didn't help either.
Any ideas? Thanks!
I used android Animation for this:
public class ProgressBarAnimation extends Animation{
private ProgressBar progressBar;
private float from;
private float to;
public ProgressBarAnimation(ProgressBar progressBar, float from, float to) {
super();
this.progressBar = progressBar;
this.from = from;
this.to = to;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
float value = from + (to - from) * interpolatedTime;
progressBar.setProgress((int) value);
}
}
and call it like so:
ProgressBarAnimation anim = new ProgressBarAnimation(progress, from, to);
anim.setDuration(1000);
progress.startAnimation(anim);
Note: if from and to value are too low to produce smooth animation just multiply them by a 100 or so. If you do so, don't forget to multiply setMax(..) as well.
The simplest way, using ObjectAnimator (both Java and Kotlin):
ObjectAnimator.ofInt(progressBar, "progress", progressValue)
.setDuration(300)
.start();
where progressValue is integer within range 0-100 (the upper limit is set to 100 by default but you can change it with Progressbar#setMax() method)
You can also change the way how values are changing by setting different interpolator with setInterpolator() method. Here is visualization of different interpolators: https://www.youtube.com/watch?v=6UL7PXdJ6-E
I use an ObjectAnimator
private ProgressBar progreso;
private ObjectAnimator progressAnimator;
progreso = (ProgressBar)findViewById(R.id.progressbar1);
progressAnimator = ObjectAnimator.ofInt(progreso, "progress", 0,1);
progressAnimator.setDuration(7000);
progressAnimator.start();
Here is an improved version of #Eli Konky solution:
public class ProgressBarAnimation extends Animation {
private ProgressBar mProgressBar;
private int mTo;
private int mFrom;
private long mStepDuration;
/**
* #param fullDuration - time required to fill progress from 0% to 100%
*/
public ProgressBarAnimation(ProgressBar progressBar, long fullDuration) {
super();
mProgressBar = progressBar;
mStepDuration = fullDuration / progressBar.getMax();
}
public void setProgress(int progress) {
if (progress < 0) {
progress = 0;
}
if (progress > mProgressBar.getMax()) {
progress = mProgressBar.getMax();
}
mTo = progress;
mFrom = mProgressBar.getProgress();
setDuration(Math.abs(mTo - mFrom) * mStepDuration);
mProgressBar.startAnimation(this);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float value = mFrom + (mTo - mFrom) * interpolatedTime;
mProgressBar.setProgress((int) value);
}
}
And usage:
ProgressBarAnimation mProgressAnimation = new ProgressBarAnimation(mProgressBar, 1000);
...
/* Update progress later anywhere in code: */
mProgressAnimation.setProgress(progress);
ProgressBar().setProgress(int progress, boolean animate)
Android has taken care of that for you
A Kotlin way of doing this
import kotlinx.android.synthetic.main.activity.*
progressBar.max = value * 100
progressBar.progress = 0
val progressAnimator = ObjectAnimator.ofInt(progressBar, "progress", progressBar.progress, progressBar.progress + 100)
progressAnimator.setDuration(7000)
progressAnimator.start()
I just wanted to add an extra value for those who want to use Data Binding with a progress bar animation.
First create the following binding adapter:
#BindingAdapter("animatedProgress")
fun setCustomProgressBar(progressBar: ProgressBar, progress: Int) {
ObjectAnimator.ofInt(progressBar, "progress", progressBar.progress, progress).apply {
duration = 500
interpolator = DecelerateInterpolator()
}.start()
}
And then use it in the layout which contains the ViewModel that reports the status updates:
<ProgressBar
android:id="#+id/progress_bar_horizontal"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:indeterminate="false"
android:max="100"
app:animatedProgress="#{viewModel.progress ?? 0}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
The ViewModel itself will report the status with the following LiveData:
private val _progress = MutableLiveData<Int?>(null)
val progress: LiveData<Int?>
get() = _
EDIT: While my answer works, Eli Konkys answer is better. Use it.
if your thread runs on the UI thread then it must surrender the UI thread to give the views a chance to update. Currently you tell the progress bar "update to 1, update to 2, update to 3" without ever releasing the UI-thread so it actually can update.
The best way to solve this problem is to use Asynctask, it has native methods that runs both on and off the UI thread:
public class MahClass extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
while (progressBar.getProgress() < progress) {
publishProgress();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Void... values) {
progressBar.incrementProgressBy(1);
}
}
AsyncTask might seem complicated at first, but it is really efficient for many different tasks, or as specified in the Android API:
"AsyncTask enables proper and easy use of the UI thread. This class
allows to perform background operations and publish results on the UI
thread without having to manipulate threads and/or handlers."
You could try using a handler / runnable instead...
private Handler h = new Handler();
private Runnable myRunnable = new Runnable() {
public void run() {
if (progressBar.getProgress() < progress) {
progressBar.incrementProgressBy(1);
progressBar.invalidate();
h.postDelayed(myRunnable, 10); //run again after 10 ms
}
};
//trigger runnable in your code
h.postDelayed(myRunnable, 10);
//don't forget to cancel runnable when you reach 100%
h.removeCallbacks(myRunnable);
Simple Kotlin solution
if (newValue != currentValue) {
ObjectAnimator.ofInt(bar, "progress", currentValue, newValue)
.setDuration(500L) // ms
.start()
}
Even simpler:
ObjectAnimator.ofInt(bar, "progress", currentValue, newValue).apply {
duration = 500L
start()
}
Here is an improved version of a.ch. solution where you can also use rotation for circular ProgressBar. Sometimes it's required to set constant progress and change only rotation or even both progress and rotation. It is also possible to force clockwise or counter clockwise rotation. I hope it will help.
public class ProgressBarAnimation extends Animation {
private ProgressBar progressBar;
private int progressTo;
private int progressFrom;
private float rotationTo;
private float rotationFrom;
private long animationDuration;
private boolean forceClockwiseRotation;
private boolean forceCounterClockwiseRotation;
/**
* Default constructor
* #param progressBar ProgressBar object
* #param fullDuration - time required to change progress/rotation
*/
public ProgressBarAnimation(ProgressBar progressBar, long fullDuration) {
super();
this.progressBar = progressBar;
animationDuration = fullDuration;
forceClockwiseRotation = false;
forceCounterClockwiseRotation = false;
}
/**
* Method for forcing clockwise rotation for progress bar
* Method also disables forcing counter clockwise rotation
* #param forceClockwiseRotation true if should force clockwise rotation for progress bar
*/
public void forceClockwiseRotation(boolean forceClockwiseRotation) {
this.forceClockwiseRotation = forceClockwiseRotation;
if (forceClockwiseRotation && forceCounterClockwiseRotation) {
// Can't force counter clockwise and clockwise rotation in the same time
forceCounterClockwiseRotation = false;
}
}
/**
* Method for forcing counter clockwise rotation for progress bar
* Method also disables forcing clockwise rotation
* #param forceCounterClockwiseRotation true if should force counter clockwise rotation for progress bar
*/
public void forceCounterClockwiseRotation(boolean forceCounterClockwiseRotation) {
this.forceCounterClockwiseRotation = forceCounterClockwiseRotation;
if (forceCounterClockwiseRotation && forceClockwiseRotation) {
// Can't force counter clockwise and clockwise rotation in the same time
forceClockwiseRotation = false;
}
}
/**
* Method for setting new progress and rotation
* #param progress new progress
* #param rotation new rotation
*/
public void setProgressAndRotation(int progress, float rotation) {
if (progressBar != null) {
// New progress must be between 0 and max
if (progress < 0) {
progress = 0;
}
if (progress > progressBar.getMax()) {
progress = progressBar.getMax();
}
progressTo = progress;
// Rotation value should be between 0 and 360
rotationTo = rotation % 360;
// Current rotation value should be between 0 and 360
if (progressBar.getRotation() < 0) {
progressBar.setRotation(progressBar.getRotation() + 360);
}
progressBar.setRotation(progressBar.getRotation() % 360);
progressFrom = progressBar.getProgress();
rotationFrom = progressBar.getRotation();
// Check for clockwise rotation
if (forceClockwiseRotation && rotationTo < rotationFrom) {
rotationTo += 360;
}
// Check for counter clockwise rotation
if (forceCounterClockwiseRotation && rotationTo > rotationFrom) {
rotationTo -= 360;
}
setDuration(animationDuration);
progressBar.startAnimation(this);
}
}
/**
* Method for setting only progress for progress bar
* #param progress new progress
*/
public void setProgressOnly(int progress) {
if (progressBar != null) {
setProgressAndRotation(progress, progressBar.getRotation());
}
}
/**
* Method for setting only rotation for progress bar
* #param rotation new rotation
*/
public void setRotationOnly(float rotation) {
if (progressBar != null) {
setProgressAndRotation(progressBar.getProgress(), rotation);
}
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float progress = progressFrom + (progressTo - progressFrom) * interpolatedTime;
float rotation = rotationFrom + (rotationTo - rotationFrom) * interpolatedTime;
// Set new progress and rotation
if (progressBar != null) {
progressBar.setProgress((int) progress);
progressBar.setRotation(rotation);
}
}
}
Usage:
ProgressBarAnimation progressBarAnimation = new ProgressBarAnimation(progressBar, 1000);
// Example 1
progressBarAnimation.setProgressAndRotation(newProgress, newRotation);
// Example 2
progressBarAnimation.setProgressOnly(newProgress);
// Example 3
progressBarAnimation.setRotationOnly(newRotation);
Similar with Kotlin on the UI thread
activity?.runOnUiThread {
ObjectAnimator.ofInt(binding.progressAudio, "progress", currentPosition)
.setDuration(100)
.start();
}
val animator = ValueAnimator.ofInt(0, 10)
animator.duration = 2000 //set duration in milliseconds
animator.addUpdateListener {
progressbar.progress = Integer.parseInt(animator.animatedValue.toString())
}
animator.start()
My solution with custom ProgressBar. You can specify animation (animationLength) legth and "smoothness" (animationSmoothness) using attributes (when you use it in XML layout)
AnimatedProgressBar.java
public class AnimatedProgressBar extends ProgressBar {
private static final String TAG = "AnimatedProgressBar";
private static final int BASE_ANIMATION_DURATION = 1000;
private static final int BASE_PROGRESS_SMOOTHNESS = 50;
private int animationDuration = BASE_ANIMATION_DURATION;
private int animationSmoothness = BASE_PROGRESS_SMOOTHNESS;
public AnimatedProgressBar(Context context) {
super(context);
init();
}
public AnimatedProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
obtainAnimationAttributes(attrs);
init();
}
public AnimatedProgressBar(Context context, AttributeSet attrs, int theme) {
super(context, attrs, theme);
obtainAnimationAttributes(attrs);
init();
}
private void obtainAnimationAttributes(AttributeSet attrs) {
for(int i = 0; i < attrs.getAttributeCount(); i++) {
String name = attrs.getAttributeName(i);
if (name.equals("animationDuration")) {
animationDuration = attrs.getAttributeIntValue(i, BASE_ANIMATION_DURATION);
} else if (name.equals("animationSmoothness")) {
animationSmoothness = attrs.getAttributeIntValue(i, BASE_PROGRESS_SMOOTHNESS);
}
}
}
private void init() {
}
#Override
public synchronized void setMax(int max) {
super.setMax(max * animationSmoothness);
}
public void makeProgress(int progress) {
ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "progress", progress * animationSmoothness);
objectAnimator.setDuration(animationDuration);
objectAnimator.setInterpolator(new DecelerateInterpolator());
objectAnimator.start();
}}
values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AnimatedProgressBar">
<attr name="animationDuration" format="integer" />
<attr name="animationSmoothness" format="integer" />
</declare-styleable>
</resources>
I need to achieve the following: animation starts, wait until it is finished and then continue with the code execution. The problem is how to "pause" the code execution while the main thread is running. OnAnimationEnd() methods are not suitable.
I can use postDelayed(Runnable r, long milliseconds) and to put all my code in the runnable, but I am not sure it is the best way to do this. Also tried with thread.sleep(), this.wait(), and another instance of thread with runnable threadnew.sleep(), but I don't see the desired behavior: it seems that wait and sleep stop the animation and the code execution, not just the code execution.
public boolean onLongClick (View v)
{
if((v==textView2)&&(androidturn == false)&&(animationongoing == false))
{
androidturn = true;
animationongoing = true;
L.startAnimation(inFromRightAnimation);
L.postDelayed(new Runnable() {
public void run() {
L.clearAnimation();
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.FILL_PARENT, FrameLayout.LayoutParams.FILL_PARENT);
params.gravity = 0x50;
params.height =710;
L.setLayoutParams(params);
//L.layout(0,top,800,bottom);
animationongoing = false;
}
}, 500);
//here I need to stop the code execution for 500ms for animation to finish
imageButton1.setBackgroundResource(R.color.Red);
imageButton1.playSoundEffect(0);
try an AsyncTask
here's an example it won't block the ui thread
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
/*setProgressPercent(progress[0]);*/
}
protected void onPostExecute(Long result) {
/* call continute method here */
}
}
sr: http://developer.android.com/reference/android/os/AsyncTask.html
Separate the part to run after into its own method, then call it immediately or in the callback as appropriate.
public boolean onLongClick (View v)
{
if((v==textView2)&&(androidturn == false)&&(animationongoing == false))
{
androidturn = true;
animationongoing = true;
inFromRightAnimation.setAnimationListener(new Animation.AnimationListener()
{
// Other listeners omitted.
public void onAnimationEnd(Animation anim) {
L.clearAnimation();
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.FILL_PARENT, FrameLayout.LayoutParams.FILL_PARENT);
params.gravity = 0x50;
params.height =710;
L.setLayoutParams(params);
//L.layout(0,top,800,bottom);
animationongoing = false;
styleImageButton();
}
});
L.startAnimation(inFromRightAnimation);
}
else
{
styleImageButton();
}
}
private void styleImageButton()
{
imageButton1.setBackgroundResource(R.color.Red);
imageButton1.playSoundEffect(0);
}
In async code, you want to avoid forcing code to wait.
EDIT: I fixed my code to use onAnimationEnd.