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>
Related
Actually I created one custom video player in my app, in this app I'm using SeekBar to show the video progress. Now I'm trying to mark SeekBar with different color at some predefined time index (e.g. 6 Sec, 20 sec and 50 sec), please check below image to understand what exactly I want--
I'm almost done with the marking functionality, but the marking is not getting match with the exact time position. Please check below images to understand my problem--
Image-1]
In this image you can clearly see that the current Thumb position is the exact 6-sec. position and the first Vertical Blue mark is actually my CustomSeekBar marking for 6 sec position.
Image-2]
Same way, in above image you can see that the current Thumb position is the exact 20-sec. position and the second Vertical Blue mark is actually my CustomSeekBar marking for 20-sec position.
Below is my "CustomSeekBar" class --
public class CustomSeekBar extends AppCompatSeekBar
{
private ArrayList<ProgressItem> mProgressItemsList;
public CustomSeekBar(Context context) {
super(context);
mProgressItemsList = new ArrayList<ProgressItem>();
}
public CustomSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSeekBar(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public void initData(ArrayList<ProgressItem> progressItemsList)
{
this.mProgressItemsList = progressItemsList;
}
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
protected void onDraw(Canvas canvas)
{
if (mProgressItemsList!=null && mProgressItemsList.size() > 0)
{
int progressBarWidth = getWidth();
int progressBarHeight = getHeight()+20;
int thumboffset = getThumbOffset()-20;
int lastProgressX = 0;
int progressItemWidth, progressItemRight;
for (int i = 0; i < mProgressItemsList.size(); i++)
{
ProgressItem progressItem = mProgressItemsList.get(i);
Paint progressPaint = new Paint();
progressPaint.setColor(getResources().getColor(
progressItem.color));
progressItemWidth = (int) (progressItem.progressItemPercentage
* progressBarWidth / 100);
progressItemRight = lastProgressX + progressItemWidth;
// for last item give right to progress item to the width
if (i == mProgressItemsList.size() - 1 && progressItemRight != progressBarWidth)
{
progressItemRight = progressBarWidth;
}
Rect progressRect = new Rect();
progressRect.set(lastProgressX, thumboffset / 2,
progressItemRight, progressBarHeight - thumboffset / 2);
canvas.drawRect(progressRect, progressPaint);
lastProgressX = progressItemRight;
}
super.onDraw(canvas);
}
}
}
Below is my ProgressItem class
public class ProgressItem
{
public int color;
public float progressItemPercentage;
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public float getProgressItemPercentage() {
return progressItemPercentage;
}
public void setProgressItemPercentage(float progressItemPercentage) {
this.progressItemPercentage = progressItemPercentage;
}
}
Below is how I'm using it in my VideoActivity--
CustomSeekBar videoProgress = (CustomSeekBar) findViewById(R.id.videoProgress);
// Disable SeekBar Thumb Drag.
videoProgress.setOnTouchListener(new View.OnTouchListener()
{
#Override
public boolean onTouch(View view, MotionEvent motionEvent)
{
return true;
}
});
/*videoProgress.getProgressDrawable().setColorFilter(getResources().getColor(R.color.cerulean_blue), PorterDuff.Mode.SRC_IN);
videoProgress.getThumb().setColorFilter(getResources().getColor(R.color.cerulean_blue), PorterDuff.Mode.SRC_IN);*/
videoProgress.getThumb().setColorFilter(getResources().getColor(R.color.cerulean_blue), PorterDuff.Mode.SRC_IN);
videoProgress.setProgress(0);
videoProgress.setMax(100);
// Function to init markers
ArrayList<ProgressItem> progressItemList;
void initVideoProgressColor()
{
progressItemList = new ArrayList<ProgressItem>();
ProgressItem mProgressItem;
mProgressItem = new ProgressItem();
int vidDuration = vidView.getDuration();
mProgressItem.progressItemPercentage = 6;
Log.e("VideoActivity", mProgressItem.progressItemPercentage + "");
mProgressItem.color = R.color.transparent_clr;
progressItemList.add(mProgressItem);
// FIRST MARKER FOR 6-SEC. POSITION
mProgressItem = new ProgressItem();
mProgressItem.progressItemPercentage = 0.5f;
mProgressItem.color = R.color.cerulean_blue;
progressItemList.add(mProgressItem);
mProgressItem = new ProgressItem();
mProgressItem.progressItemPercentage = 20;
mProgressItem.color = R.color.transparent_clr;
progressItemList.add(mProgressItem);
// SECOND MARKER FOR 20-SEC. POSITION
mProgressItem = new ProgressItem();
mProgressItem.progressItemPercentage = 0.5f;
mProgressItem.color = R.color.cerulean_blue;
progressItemList.add(mProgressItem);
mProgressItem = new ProgressItem();
mProgressItem.progressItemPercentage = 70;
mProgressItem.color = R.color.transparent_clr;
progressItemList.add(mProgressItem);
videoProgress.initData(progressItemList);
videoProgress.invalidate();
}
for more details, please check below link which I refereed to implement this Custom SeekBar-
https://www.androiddevelopersolutions.com/2015/01/android-custom-horizontal-progress-bar.html
Also, I tried solution from below link, but unfortunately getting the same result--
android seek bar customization,
Actually I'm very close to the answer, just need a proper guidance which I think I'll get from you experts. Please let me know if I can provide more details for the same. Thank you.
Finally I got the solution. Below are the steps to implement the solution--
Step-1] Create one "attrs.xml" file in "res/values/" folder and paste below code in that file--
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DottedSeekBar">
<attr name="dots_positions" format="reference"/>
<attr name="dots_drawable" format="reference"/>
</declare-styleable>
</resources>
Step-2] Prepare one image icon which you want to use to mark on progress bar and name it "video_mark.png".
Step-3] Create one custom SeekBar as below--
public class DottedSeekBar extends AppCompatSeekBar {
/** Int values which corresponds to dots */
private int[] mDotsPositions = null;
/** Drawable for dot */
private Bitmap mDotBitmap = null;
public DottedSeekBar(final Context context) {
super(context);
init(null);
}
public DottedSeekBar(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public DottedSeekBar(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
/**
* Initializes Seek bar extended attributes from xml
*
* #param attributeSet {#link AttributeSet}
*/
private void init(final AttributeSet attributeSet) {
final TypedArray attrsArray = getContext().obtainStyledAttributes(attributeSet, R.styleable.DottedSeekBar, 0, 0);
final int dotsArrayResource = attrsArray.getResourceId(R.styleable.DottedSeekBar_dots_positions, 0);
if (0 != dotsArrayResource) {
mDotsPositions = getResources().getIntArray(dotsArrayResource);
}
final int dotDrawableId = attrsArray.getResourceId(R.styleable.DottedSeekBar_dots_drawable, 0);
if (0 != dotDrawableId) {
mDotBitmap = BitmapFactory.decodeResource(getResources(), dotDrawableId);
}
}
/**
* #param dots to be displayed on this SeekBar
*/
public void setDots(final int[] dots) {
mDotsPositions = dots;
invalidate();
}
/**
* #param dotsResource resource id to be used for dots drawing
*/
public void setDotsDrawable(final int dotsResource)
{
mDotBitmap = BitmapFactory.decodeResource(getResources(), dotsResource);
invalidate();
}
#Override
protected synchronized void onDraw(final Canvas canvas) {
super.onDraw(canvas);
final float width=getMeasuredWidth()-getPaddingLeft()-getPaddingRight();
final float step=width/(float)(getMax());
if (null != mDotsPositions && 0 != mDotsPositions.length && null != mDotBitmap) {
// draw dots if we have ones
for (int position : mDotsPositions) {
canvas.drawBitmap(mDotBitmap, position * step, 0, null);
}
}
}
}
Step-4] Use this custom SeekBar in your activity.xml file as below--
<com.your_package.DottedSeekBar
android:id="#+id/videoProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Step-5] Add below code in "onCreate()" method of your "Activity.java" class--
DottedSeekBar videoProgress = (DottedSeekBar) findViewById(R.id.videoProgress);
// Disable SeekBar Thumb Drag. (Optional)
videoProgress.setOnTouchListener(new View.OnTouchListener()
{
#Override
public boolean onTouch(View view, MotionEvent motionEvent)
{
return true;
}
});
// Set custom thumb icon color here (Optional)
videoProgress.getThumb().setColorFilter(getResources().getColor(R.color.cerulean_blue), PorterDuff.Mode.SRC_IN);
// Add below line to avoid unnecessary SeekBar padding. (Optional)
videoProgress.setPadding(0, 0, 0, 0);
// Handler to update video progress time--
handler = new Handler();
// Define the code block to be executed
final Runnable runnableCode = new Runnable() {
#Override
public void run()
{
updateCurrentTime();
// Repeat this the same runnable code block again another 1 seconds
// 'this' is referencing the Runnable object
handler.postDelayed(this, 1000);
}
};
Use "videoView.setOnPreparedListener()" method to calculate total video time in seconds
yourVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
{
#Override
public void onPrepared(MediaPlayer mp)
{
String strTotalDuration = msToTimeConverter(vidView.getDuration());
String[] strTimeArr = strTotalDuration.split(":");
int min = Integer.parseInt(strTimeArr[0]);
int videoLengthInSec = Integer.parseInt(strTimeArr[1]);
videoLengthInSec = videoLengthInSec + (min*60);
videoProgress.setProgress(0);
videoProgress.setMax(videoLengthInSec);
// Start the initial runnable task by posting through the handler
handler.post(runnableCode);
initVideoMarkers();
}
}
);
Step-6] Copy below required methods in your "Activity.java" class--
// Method to update time progress
private void updateCurrentTime()
{
if (videoProgress.getProgress() >= 100)
{
handler.removeMessages(0);
}
String currentPosition = msToTimeConverter(vidView.getCurrentPosition());
String[] strArr = currentPosition.split(":");
int progress = vidView.getCurrentPosition() * videoLengthInSec / vidView.getDuration();
videoProgress.setProgress(progress);
}
// Milliseconds to Time converter Method
String msToTimeConverter(int millis)
{
return String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)),
TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
}
// Method to set Marker values
private void initVideoMarkers()
{
// Here I'm adding markers on 10, 15 and 20 Second index
videoProgress.setDots(new int[] {10, 15, 20});
videoProgress.setDotsDrawable(R.drawable.video_mark);
}
Actually I implemented custom video player in my app, also I'm showing video progress using SeekBar View. Now I want to show some marker just like "Youtube" Ad marker on my video progress bar, the number of markers and their respective position/index will be decide at runtime. I found this question (How to mark horizontal ProgressBar with different color at some index just like Youtube video yellow color ad marker in Android) asked before on stackoverflow, but unfortunately there is no solution yet. Please check below image to understand what exactly I want to achieve.
https://drive.google.com/open?id=1qxfsTu8WOPMIlek7616rVQgzab8CFVcp
Any help will be really very appreciated. Thank you.
Hi #PKMBSD I know its quite late, but check my answer below which will surely help you to achieve what you want--
Step-1] Create one "attrs.xml" file in "res/values/" folder and paste below code in that file--
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DottedSeekBar">
<attr name="dots_positions" format="reference"/>
<attr name="dots_drawable" format="reference"/>
</declare-styleable>
</resources>
Step-2] Prepare one image icon which you want to use to mark on progress bar and name it "video_mark.png".
Step-3] Create one custom SeekBar as below--
public class DottedSeekBar extends AppCompatSeekBar {
/** Int values which corresponds to dots */
private int[] mDotsPositions = null;
/** Drawable for dot */
private Bitmap mDotBitmap = null;
public DottedSeekBar(final Context context) {
super(context);
init(null);
}
public DottedSeekBar(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public DottedSeekBar(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
/**
* Initializes Seek bar extended attributes from xml
*
* #param attributeSet {#link AttributeSet}
*/
private void init(final AttributeSet attributeSet) {
final TypedArray attrsArray = getContext().obtainStyledAttributes(attributeSet, R.styleable.DottedSeekBar, 0, 0);
final int dotsArrayResource = attrsArray.getResourceId(R.styleable.DottedSeekBar_dots_positions, 0);
if (0 != dotsArrayResource) {
mDotsPositions = getResources().getIntArray(dotsArrayResource);
}
final int dotDrawableId = attrsArray.getResourceId(R.styleable.DottedSeekBar_dots_drawable, 0);
if (0 != dotDrawableId) {
mDotBitmap = BitmapFactory.decodeResource(getResources(), dotDrawableId);
}
}
/**
* #param dots to be displayed on this SeekBar
*/
public void setDots(final int[] dots) {
mDotsPositions = dots;
invalidate();
}
/**
* #param dotsResource resource id to be used for dots drawing
*/
public void setDotsDrawable(final int dotsResource)
{
mDotBitmap = BitmapFactory.decodeResource(getResources(), dotsResource);
invalidate();
}
#Override
protected synchronized void onDraw(final Canvas canvas) {
super.onDraw(canvas);
final float width=getMeasuredWidth()-getPaddingLeft()-getPaddingRight();
final float step=width/(float)(getMax());
if (null != mDotsPositions && 0 != mDotsPositions.length && null != mDotBitmap) {
// draw dots if we have ones
for (int position : mDotsPositions) {
canvas.drawBitmap(mDotBitmap, position * step, 0, null);
}
}
}
}
Step-4] Use this custom SeekBar in your activity.xml file as below--
<com.your_package.DottedSeekBar
android:id="#+id/videoProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Step-5] Add below code in "onCreate()" method of your "Activity.java" class--
DottedSeekBar videoProgress = (DottedSeekBar) findViewById(R.id.videoProgress);
// Disable SeekBar Thumb Drag. (Optional)
videoProgress.setOnTouchListener(new View.OnTouchListener()
{
#Override
public boolean onTouch(View view, MotionEvent motionEvent)
{
return true;
}
});
// Set custom thumb icon here (Optional)
videoProgress.getThumb().setColorFilter(getResources().getColor(R.color.cerulean_blue), PorterDuff.Mode.SRC_IN);
// Add below line to avoid unnecessary SeekBar padding. (Optional)
videoProgress.setPadding(0, 0, 0, 0);
// Handler to update video progress time--
handler = new Handler();
// Define the code block to be executed
final Runnable runnableCode = new Runnable() {
#Override
public void run()
{
updateCurrentTime();
// Repeat this the same runnable code block again another 1 seconds
// 'this' is referencing the Runnable object
handler.postDelayed(this, 1000);
}
};
yourVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
{
#Override
public void onPrepared(MediaPlayer mp)
{
String strTotalDuration = msToTimeConverter(vidView.getDuration());
String[] strTimeArr = strTotalDuration.split(":");
int min = Integer.parseInt(strTimeArr[0]);
int videoLengthInSec = Integer.parseInt(strTimeArr[1]);
videoLengthInSec = videoLengthInSec + (min*60);
videoProgress.setProgress(0);
videoProgress.setMax(videoLengthInSec);
// Start the initial runnable task by posting through the handler
handler.post(runnableCode);
initVideoMarkers();
}
}
);
Step-6] Copy below required methods in your "Activity.java" class--
// Method to update time progress
private void updateCurrentTime()
{
if (videoProgress.getProgress() >= 100)
{
handler.removeMessages(0);
}
String currentPosition = msToTimeConverter(vidView.getCurrentPosition());
String[] strArr = currentPosition.split(":");
int progress = vidView.getCurrentPosition() * videoLengthInSec / vidView.getDuration();
videoProgress.setProgress(progress);
}
// Milliseconds to Time converter Method
String msToTimeConverter(int millis)
{
return String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)),
TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
}
// Method to set Marker values
private void initVideoMarkers()
{
videoProgress.setDots(new int[] {10, 15, 20});
videoProgress.setDotsDrawable(R.drawable.video_mark);
}
I had the same challenge and I'm using this solution, it works 100% fine. Hope it will help you.
Could someone please post a simple example of how to animate a TextView growing in height from 0dp to a dynamic value?
I would like to increase the height of a textview, but allow the user to SEE it like an animation. The only condition is I know my max height is variable and not known at run time. The maximum I want to allow is 100dp.
I've tried performing a simple for loop such as:
runOnUiThread(new Runnable() {
public void run() {
LayoutParams params = myTextView.getLayoutParams();
int myMaxHeightValue = 100;
for (int x=0; x<myMaxHeightValue; x++) {
params.height = getDensityPixels(x);
myTextView.setLayoutParams(params);
Thread.Sleep(300);
}
}
});
public void getDensityPixels(int value) {
float density = getResources().getDisplayMetrics.display;
return (int) (value * density + 0.5f);
}
Can anyone provide just a simple example where I can provide the MAX height and watch the textview grow from 0dp height to the MAX dp height? When I use the code above, it does indeed increase to the max height of 100dp, but the screen locks while it is sizing rather than SHOWING me the increase as it occurs.
Any help is greatly appreciated!
Never use thread to perform UI/View animations. Android provide so many options for animation, like ObjectAnimator, ValueAnimator, View Animator
An example, as per your requirement.
private void animateHeight() {
TextView myTextView=null;
int maxInDp = 100;
int maxInPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
maxInDp, mLauncher.getResources().getDisplayMetrics());
ObjectAnimator.ofInt(this, "btnHeight", maxInPx).setDuration(300).start(); // for better animation change duration , and set interpolator.
}
public int btnHeight = 0;
public int getBtnHeight() {
return btnHeight;
}
public void setBtnHeight(int height) {
btnHeight = height;
LayoutParams params = myTextView.getLayoutParams();
params.height = btnHeight;
myTextView.setLayoutParams(params);
}
You are locking your Main thread by running your worker thread on it. To update your UI while doing work in the background, use an AsyncTask. This tutorial explains how to do it nicely.
Basically, your AsyncTask will look something like this (example from above tutorial)
public class DoSomethingTask extends AsyncTask<Void, String, Void> {
private static final String TAG = "DoSomethingTask";
private static final int DELAY = 5000; // 5 seconds
private static final int RANDOM_MULTIPLIER = 10;
#Override
protected void onPreExecute() {
Log.v(TAG, "starting the Random Number Task");
super.onPreExecute();
}
#Override
protected void onProgressUpdate(String... values) {
//HERE is where you will be doing your UI updates
Log.v(TAG, "reporting back from the Random Number Task");
updateResults(values[0].toString());
super.onProgressUpdate(values);
}
#Override
protected Void doInBackground(Void... params) {
//HERE is where you will be doing your work (your loop)
Log.v(TAG, "doing work in Random Number Task");
String text="";
while (true) {
if (isCancelled()) {
break;
}
int randNum = (int) (Math.random() * RANDOM_MULTIPLIER);
text = String.format(getString(R.string.service_msg), randNum);
//This is how you tell your thread to update the UI
publishProgress(text);
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
Log.v(TAG, "Interrupting the Random Number Task");
}
}
return null;
}
}
I want to slowdown SlidingDrawer opening speed using interpolator (DecelerateInterpolator)
Is this possible. i want to implement ease animation.
final Animation slowDown = AnimationUtils.loadAnimation(((Swipe)getActivity()), R.anim.ease);
this is XML
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/decelerate_interpolator">
<translate
android:fromYDelta="0"
android:toYDelta="100%p"
android:duration="2000"/>
</set>
Using this i am not getting what i want.
Actually, if You want only open/close that sliding layout, than I would suggest to use custom ViewGroup instead of deprecated SlidingDrawer. Below is some simple example of that implementation:
public class MySimpleSlidingDrawer extends RelativeLayout implements View.OnClickListener {
private static final int SLIDING_TIME = 500;
private View mHandle;
private int mHandleId;
private View mContent;
private int mContentId;
private ObjectAnimator mOpenAnimator = ObjectAnimator.ofFloat(this, "slide", 0f, 1f);
private ObjectAnimator mCloseAnimator = ObjectAnimator.ofFloat(this, "slide", 1f, 0f);
private int mAnimationTime = SLIDING_TIME;
private boolean mOpened = false;
private int mSlideHeight = 0;
public MySimpleSlidingDrawer(final Context context) {
super(context);
init(context, null, 0);
}
public MySimpleSlidingDrawer(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public MySimpleSlidingDrawer(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
private void init(final Context context, final AttributeSet attrs, final int defStyle) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MySlidingDrawer, defStyle, 0);
mHandleId = a.getResourceId(R.styleable.MySlidingDrawer_handle, 0);
mContentId = a.getResourceId(R.styleable.MySlidingDrawer_content, 0);
mOpenAnimator.setInterpolator(new AccelerateInterpolator());
mOpenAnimator.setDuration(SLIDING_TIME);
mCloseAnimator.setInterpolator(new DecelerateInterpolator());
mCloseAnimator.setDuration(SLIDING_TIME);
setClipChildren(false);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() != 2) {
throw new InflateException("Only to child are supported for this layout");
}
if (mHandleId != 0) {
mHandle = findViewById(mHandleId);
} else {
mHandle = getChildAt(0);
}
if (mContentId != 0) {
mContent = findViewById(mContentId);
} else {
mContent = getChildAt(1);
}
final LayoutParams handleParams = (LayoutParams) mHandle.getLayoutParams();
handleParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
handleParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
handleParams.addRule(ALIGN_PARENT_BOTTOM, 1/* true */);
handleParams.addRule(CENTER_HORIZONTAL, 1/* true */);
mHandle.setLayoutParams(handleParams);
mHandle.setOnClickListener(this);
}
#Override
public void onClick(final View v) {
if (mSlideHeight == 0) {
mSlideHeight = getHeight() - mHandle.getHeight();
}
// Handle have been clicked. Execute animation depending on open / close state
if (!mOpened) {
mOpened = true;
mCloseAnimator.cancel();
mOpenAnimator.start();
} else {
mOpened = false;
mOpenAnimator.cancel();
mCloseAnimator.start();
}
}
/**
* Sets slide percent value
*
* #param slidePercent % of slide (0 - closed, 1 - opened)
*/
#SuppressWarnings("UnusedDeclaration")
public void setSlide(final float slidePercent) {
final LayoutParams handleParams = (LayoutParams) mHandle.getLayoutParams();
handleParams.bottomMargin = (int) (slidePercent * mSlideHeight);
mHandle.setLayoutParams(handleParams);
final LayoutParams contentParams = (LayoutParams) mContent.getLayoutParams();
contentParams.bottomMargin = (int) -((1- slidePercent) * mSlideHeight);
mContent.setLayoutParams(contentParams);
}
/**
* Sets open interpolator
*
* #param interpolator {#link android.view.animation.Interpolator} for open animation
*/
#SuppressWarnings("UnusedDeclaration")
public void setOpenInterpolator(final Interpolator interpolator) {
if (mOpenAnimator.isRunning()) {
mOpenAnimator.cancel();
}
mOpenAnimator.setInterpolator(interpolator);
}
/**
* Sets close interpolator
*
* #param interpolator {#link android.view.animation.Interpolator} for close animation
*/
#SuppressWarnings("UnusedDeclaration")
public void setCloseInterpolator(final Interpolator interpolator) {
if (mCloseAnimator.isRunning()) {
mCloseAnimator.cancel();
}
mCloseAnimator.setInterpolator(interpolator);
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Trick to avoid content to be resized - measure it as it visible
final int contentHeightMeasure = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - mHandle.getMeasuredHeight(), MeasureSpec.EXACTLY);
final int contentWidthMeasure = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
mContent.measure(contentHeightMeasure, contentWidthMeasure);
if (mSlideHeight == 0) {
mSlideHeight = getMeasuredHeight() - mHandle.getMeasuredHeight();
final LayoutParams contentParams = (LayoutParams) mContent.getLayoutParams();
contentParams.height = mSlideHeight;
contentParams.addRule(ALIGN_PARENT_BOTTOM, 1 /* true */);
contentParams.bottomMargin = - mSlideHeight;
mContent.setLayoutParams(contentParams);
}
}
}
NOTE: it's not optimized or tested a lot, but basic functionality works.
Another way is to adapt existing SlidingDrawer code to your need. For me it looks not so easy or flexible, because of its existing implementation specific. First of all, it clear mentioned in SlidingDrawer documentation:
This class is not supported anymore. It is recommended you base your
own implementation on the source code for the Android Open Source
Project if you must use it in your application.
And there's no animation change API exposed by SlidingDrawer. The main problem is that there's no animation at all, some timing event is just sent to update view position, here:
private void doAnimation() {
if (mAnimating) {
incrementAnimation();
if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) {
mAnimating = false;
closeDrawer();
} else if (mAnimationPosition < mTopOffset) {
mAnimating = false;
openDrawer();
} else {
moveHandle((int) mAnimationPosition);
mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
mCurrentAnimationTime);
}
}
}
So, in order to modify interpolation, you will need to change what animation logic or provide your own. Second way is safer, because doesn't affect existing logic. Below is some draft variant how to do it (to detect exact changes comparing with original SlidingDrawer open below class diff with original API 19 version from your android SDK installation), only changed code is presented below:
public class MySlidingDrawer extends ViewGroup {
/** Click animation duration */
// TODO: ideally you should properly calculate that value
private static final long CLICK_ANIMATION_DURATION = 1000;
/** New field for custom interpolator */
private TimeInterpolator mAnimationInterpolator = new BounceInterpolator();
/** just to distinguish click and moving by user animations */
private boolean mAnimatedClick = false;
/** Specific click animator */
private ObjectAnimator mClickToggleAnimation;
/** Specific listener just to handle animation end properly */
private Animator.AnimatorListener mClickAnimationListener = new Animator.AnimatorListener() {
#Override
public void onAnimationStart(final Animator animation) {
// nothing to do here
}
#Override
public void onAnimationEnd(final Animator animation) {
mAnimating = false;
// Determine if it close or open, by comparing to some final value
if (mAnimationPosition == mTopOffset) {
openDrawer();
} else {
closeDrawer();
}
}
#Override
public void onAnimationCancel(final Animator animation) {
// TODO: should be handled properly
}
#Override
public void onAnimationRepeat(final Animator animation) {
}
};
...
/**
* Creates a new SlidingDrawer from a specified set of attributes defined in XML.
*
* #param context The application's environment.
* #param attrs The attributes defined in XML.
*/
public MySlidingDrawer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
...
#Override
public boolean onTouchEvent(MotionEvent event) {
if (mLocked) {
return true;
}
if (mTracking) {
mVelocityTracker.addMovement(event);
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE:
moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(mVelocityUnits);
float yVelocity = velocityTracker.getYVelocity();
float xVelocity = velocityTracker.getXVelocity();
boolean negative;
final boolean vertical = mVertical;
if (vertical) {
negative = yVelocity < 0;
if (xVelocity < 0) {
xVelocity = -xVelocity;
}
if (xVelocity > mMaximumMinorVelocity) {
xVelocity = mMaximumMinorVelocity;
}
} else {
negative = xVelocity < 0;
if (yVelocity < 0) {
yVelocity = -yVelocity;
}
if (yVelocity > mMaximumMinorVelocity) {
yVelocity = mMaximumMinorVelocity;
}
}
float velocity = (float) Math.hypot(xVelocity, yVelocity);
if (negative) {
velocity = -velocity;
}
final int top = mHandle.getTop();
final int left = mHandle.getLeft();
if (Math.abs(velocity) < mMaximumTapVelocity) {
if (vertical ? (mExpanded && top < mTapThreshold + mTopOffset) ||
(!mExpanded && top > mBottomOffset + getBottom() - getTop() -
mHandleHeight - mTapThreshold) :
(mExpanded && left < mTapThreshold + mTopOffset) ||
(!mExpanded && left > mBottomOffset + getRight() - getLeft() -
mHandleWidth - mTapThreshold)) {
if (mAllowSingleTap) {
playSoundEffect(SoundEffectConstants.CLICK);
animateToggle();
/*
if (mExpanded) {
animateClose(vertical ? top : left);
} else {
animateOpen(vertical ? top : left);
}
*/
} else {
performFling(vertical ? top : left, velocity, false);
}
} else {
performFling(vertical ? top : left, velocity, false);
}
} else {
performFling(vertical ? top : left, velocity, false);
}
}
break;
}
}
return mTracking || mAnimating || super.onTouchEvent(event);
}
...
/**
* Toggles the drawer open and close with an animation.
*
* #see #open()
* #see #close()
* #see #animateClose()
* #see #animateOpen()
* #see #toggle()
*/
public void animateToggle() {
mAnimatedClick = true;
if (!mExpanded) {
animateClickOpen();
} else {
animateClickClose();
}
}
/**
* For doing our animation for close
*/
private void animateClickClose() {
mAnimating = true;
mClickToggleAnimation = ObjectAnimator.ofInt(this, "togglePosition", (int) mAnimationPosition, mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1);
mClickToggleAnimation.setInterpolator(mAnimationInterpolator);
mClickToggleAnimation.setDuration(CLICK_ANIMATION_DURATION);
mClickToggleAnimation.addListener(mClickAnimationListener);
mClickToggleAnimation.start();
}
/**
* For doing our animation for open
*/
private void animateClickOpen() {
mAnimating = true;
mClickToggleAnimation = ObjectAnimator.ofInt(this, "togglePosition", (int)mAnimationPosition, mTopOffset);
mClickToggleAnimation.setInterpolator(mAnimationInterpolator);
mClickToggleAnimation.setDuration(CLICK_ANIMATION_DURATION);
mClickToggleAnimation.addListener(mClickAnimationListener);
mClickToggleAnimation.start();
}
/**
* Sets current animation position
*
* #param position to be set
*/
#SuppressWarnings("UnusedDeclaration")
public void setTogglePosition(final int position) {
mAnimationPosition = position;
moveHandle((int) mAnimationPosition);
}
...
private class DrawerToggler implements OnClickListener {
public void onClick(View v) {
if (mLocked) {
return;
}
// mAllowSingleTap isn't relevant here; you're *always*
// allowed to open/close the drawer by clicking with the
// trackball.
if (mAnimateOnClick) {
animateToggle();
} else {
toggle();
}
}
}
...
/**
* New API to modify timing of interpolator
*
* #param interpolator {#link android.animation.TimeInterpolator} to be used for onClick open / close
*
* TODO: it's also possible to add XML attribute for the same
*/
public void setAnimationInterpolator(final TimeInterpolator interpolator) {
mAnimationInterpolator = interpolator;
}
}
And there's xml I've used for testing (only one sliding drawer section should be enabled):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.alexstarc.testapp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- Some fragment with main content of activity screen -->
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.alexstarc.testapp.VoteListFragment"
android:tag="mainFragment"/>
<com.alexstarc.testapp.MySlidingDrawer
android:id="#+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:handle="#+id/handle"
custom:orientation="vertical"
custom:content="#+id/content">
<ImageView
android:id="#id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#android:drawable/ic_delete"/>
<ImageView
android:id="#id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/cover"
android:scaleType="fitXY"/>
</com.alexstarc.testapp.MySlidingDrawer>
<!--
<com.alexstarc.testapp.MySimpleSlidingDrawer
android:id="#+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:handle="#+id/handle"
custom:content="#+id/content">
<ImageView
android:id="#id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#android:drawable/ic_delete"/>
<ImageView
android:id="#id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/cover"
android:scaleType="fitXY"/>
</com.alexstarc.testapp.MySimpleSlidingDrawer>
-->
</RelativeLayout>
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.