I'm trying to fake some kind of progress bar. I have X images and want an ImageView to show them with a certain delay.
I've tried to do something like this:
for(i=2;i<X;i++)
{
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
drawable = getResources().getDrawable(getResources()
.getIdentifier("img_"+i, "drawable", getPackageName()));
imgPayment.setImageDrawable(drawable);
}}, DELAY);
}
But the for loop doesn't wait for the run() to end. I just see the first and last image. I tried a few other things but couldn't get the desired results.
Why are you not using an Animation Drawable? It seems that you're not loading the images dynamically.
<animation-list android:id="#+id/selected" android:oneshot="false">
<item android:drawable="#drawable/wheel0" android:duration="50" />
<item android:drawable="#drawable/wheel1" android:duration="50" />
...
</animation-list>
Did I miss something?
Link to the Docs
Link to sample
As the sample docs state:
It's important to note that the start() method called on the
AnimationDrawable cannot be called during the onCreate() method of
your Activity, because the AnimationDrawable is not yet fully attached
to the window. If you want to play the animation immediately, without
requiring interaction, then you might want to call it from the
onWindowFocusChanged() method in your Activity, which will get called
when Android brings your window into focus.
What about using Thread.sleep()? If you set sleeping interval of each image in an incremental manner, they will be displayed as if they are ordered.
for (i = 1; i < X; i++)
{
//Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run()
{
Thread.sleep(i * 1000); // REMARK HERE!
drawable = getResources().getDrawable(getResources().getIdentifier
("img_" + i, "drawable", getPackageName()));
imgPayment.setImageDrawable(drawable);
}
});
}
However, to be able to use the variable i as in the code above, declare it as a global static variable.
static int i = 0;
I would recommend using an ASyncTask and then update your drawable from there or using a callback.
Related
I am learning Android's new SplashScreen API introduced with Android 12. I have so far gotten it to work on my Emulator and Google Pixel 4A, but I want to increase its duration. In my Splash Screen I do not want a fancy animation, I just want a static drawable.
I know, I know (sigh) some of you might be thinking, that I should not increase the duration and I know there are several good arguments in favor of not doing so. However, for me the duration of a splash screen with a non animated drawable is so brief (less than a second), I think it raises an accessibility concern, especially so since it cannot be disabled (ironically). Simply, the organization behind the product or its brand/product identity cannot be properly absorbed or recognized by a new user at that size and in that time, rendering the new splash screen redundant.
I see the property windowSplashScreenAnimationDuration in the theme for the splash screen (shown below), but this has no effect on the duration presumably because I am not animating.
<style name="Theme.App.starting" parent="Theme.SplashScreen">
<!--Set the splash screen background, animated icon, and animation duration.-->
<item name="windowSplashScreenBackground">#color/gold</item>
<!-- Use windowSplashScreenAnimatedIcon to add either a drawable or an
animated drawable. One of these is required-->
<item name="windowSplashScreenAnimatedIcon">#drawable/accessibility_today</item>
<item name="windowSplashScreenAnimationDuration">300</item> <!--# Required for-->
<!--# animated icons-->
<!--Set the theme of the activity that directly follows your splash screen-->
<item name="postSplashScreenTheme">#style/Theme.MyActivity</item>
<item name="android:windowSplashScreenBrandingImage">#drawable/wculogo</item>
</style>
Is there a straightforward way to extend the duration of a non animated splash screen?
As I was writing this question and almost ready to post it, I stumbled on the method setKeepOnScreenCondition (below) that belongs to the splashScreen that we must install on the onCreate of our main activity. I thought it seemed wasteful not to post this, given there are no other posts on this topic and no such similar answers to other related questions (as of Jan 2022).
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
plashScreen.setKeepOnScreenCondition(....);
Upon inspecting it I found this method receives an instance of the splashScreen.KeepOnScreenCondition() interface for which the implementation must supply the following method signature implementation:
public boolean shouldKeepOnScreen()
It seems this method will be called by the splash screen and retain the splash screen visibly until it returns false. This is where the light bulb moment I so love about programming occurred.
What if I use a boolean initialized as true, and set it to false after a delay? That hunch turned out to work. Here is my solution. It seems to work and I thought it would be useful to others. Presumably instead of using a Handler for a delay, one could also use this to set the boolean after some process had completed.
package com.example.mystuff.myactivity;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.splashscreen.SplashScreen;
import android.os.Bundle;
import android.os.Handler;
public class MainActivity extends AppCompatActivity {
private boolean keep = true;
private final int DELAY = 1250;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Handle the splash screen transition.
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
super.onCreate(savedInstanceState);
//Keep returning false to Should Keep On Screen until ready to begin.
splashScreen.setKeepOnScreenCondition(new SplashScreen.KeepOnScreenCondition() {
#Override
public boolean shouldKeepOnScreen() {
return keep;
}
});
Handler handler = new Handler();
handler.postDelayed(runner, DELAY);
}
/**Will cause a second process to run on the main thread**/
private final Runnable runner = new Runnable() {
#Override
public void run() {
keep = false;
}
};
}
If you are into Java Lambdas an even nicer and more compact solution is as follows:
package com.example.mystuff.myactivity;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.splashscreen.SplashScreen;
import android.os.Bundle;
import android.os.Handler;
public class MainActivity extends AppCompatActivity {
private boolean keep = true;
private final int DELAY = 1250;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Handle the splash screen transition.
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Keep returning false to Should Keep On Screen until ready to begin.
splashScreen.setKeepOnScreenCondition(() -> keep);
Handler handler = new Handler();
handler.postDelayed(() -> keep = false, DELAY);;
}
}
If you have comments or feedback (besides telling me I should not increase the duration of the splash screen), or a better way please do comment or respond with additional answers.
in Kotlin:
var keepSplashOnScreen = true
val delay = 2000L
installSplashScreen().setKeepOnScreenCondition { keepSplashOnScreen }
Handler(Looper.getMainLooper()).postDelayed({ keepSplashOnScreen = false }, delay)
you can put this into onCreate fun before super.onCreate calling (in activity with LAUNCHER intent filter in Manifest)
One proxy way could be to use
runBlocking { delay(1200) } in onCreate method, to keep on main thread for some specific time.
I am just starting to learn Android, Java and need help.
I have an activity with the countdowntimer, which works fine. However, I want it to be displayed in the fragment. What is the best way to do it?
I tried calling Timer.getCountdowntimer, I tried calling Timer.getUserTime (userTime is the user selected time for the countdowntimer), but the textview in my fragment doesn't display the timer.
thanks in advance!
If you are coding in Java purely, and want to use the android SDK to do it, I would recommend:-
//Create a handler that runs on main loop so we can update UX
final android.os.Handler handler = new android.os.Handler(Looper.getMainLooper());
//Get a callback in 1 second
handler.postDelayed(new Runnable() {
int timer;
#Override
public void run() {
timer += 1;
myTextView.setText(String.valueOf(timer));
//Recursively get another callback in a second
handler.postDelayed(this, 1000);
}
}, 1000);
Make sure you add some logic to stop the timer when you want, and also onPause/onResume
I am creating like a Whack a Molee type of game, and I am creating imageviews at random positions on the screen.
The thing is, I am adding a postdelayed to a var I am creating every two seconds ( the var is the imageview) but it only acts to the last imageview created. It happens because every two seconds that var is replaced with a new instance of imageview.
How can I add an independent postdelayed or something that detects when 2 seconds has passed and removing that specific imageview.
Thanks!
mv = new ImageView(this);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mv.setVisibility(View.GONE);
}
}, 2000);
I tried this, but only removes the very last imageview.
public void createImage() {
final ImageView mv = new ImageView(this);
// Do all your positioning and sizing (layout params) setting stuff here
new Handler(getMainLooper()).postDelayed(new Runnable() {
#Override
public void run() {
mv.setVisibility(View.GONE);
}
}, 2000);
}
Simply don't override the value every time, by using a local varaible inside a method.
This way the mv instance will be the same in the runnable which is executed by the handler.
(And also don't create a handler every time)
(You could also draw the entire thing on a canvas)
I want to create a Button in Android with a text, and a background image. The background image should crossfade every X time.
I have this working using a TransitionDrawable with 2 images.
But I can't get this to work with more than 2 images.
What I have :
In Java code I create a Button and set a background (which is a TransitionDrawable defined in XML). And I start the transition.
final Button b = new Button(getApplicationContext());
b.setTextColor(getResources().getColor(R.color.white));
b.setText("Some text");
b.setBackgroundDrawable(getResources().getDrawable(R.drawable.tile));
StateListDrawable background = (StateListDrawable) b.getBackground();
TransitionDrawable td = (TransitionDrawable) background.getCurrent();
td.startTransition(2000);
In XML I define in tile.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape>
<solid
android:color="#449def" />
</shape>
</item>
<item android:drawable="#drawable/transition">
<shape>
<solid
android:color="#0000ff" />
</shape>
</item>
</selector>
And finally a transition.xml
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:drawable="#drawable/desert"/>
<item android:drawable="#drawable/hydrangeas" />
<item android:drawable="#drawable/jellyfish" />
</transition>
Now the effect is that when I start the app the desert image is shown. This image crossfades to the hydrangeas image as it should. But the jellyfish image is never shown.
In the doc for TransitionDrawables it is stated that you can specify more than 2 drawables but I can't get this to work.
I also tried this without any XML but in pure JAVA but this gave exactly the same problem :-(
You can do it by using a handler
mAnimateImage is your button
int DrawableImage[] = {R.drawable.back_red, R.drawable.back_green, R.drawable.back_purple};
final Handler handler = new Handler();
final int[] i = {0};
final int[] j = {1};
handler.postDelayed(new Runnable() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
Resources res = getApplicationContext().getResources();
TransitionDrawable out = new TransitionDrawable(new Drawable[]{res.getDrawable(DrawableImage[i[0]]), res.getDrawable(DrawableImage[j[0]])});
out.setCrossFadeEnabled(true);
mAnimateImage.setBackgroundDrawable(out);
out.startTransition(4000);
i[0]++;
j[0]++;
if (j[0] == DrawableImage.length) {
j[0] = 0;
}
if (i[0] == DrawableImage.length) {
i[0] = 0;
}
handler.postDelayed(this, 8000);
}
});
}
}, 0);
According to the official documentation, TransitionDrawable can only cross-fade among 2 layers, quoting from the official android reference.
An extension of LayerDrawables that is intended to cross-fade between
the first and second layer. To start the transition, call
startTransition(int). To display just the first layer, call
resetTransition().
If you don't read it carefully, since it extends LayerDrawables, which can have multiple layers, one may expect that you could cross-fade from N layers. But it is very clear, startTransition shows the second layer, resetTransition shows the first.
I suggest you do your own implementation for multiple transitions. What I'd do is to have 2 images and animate them. You may need to set the drawables by hand, but it should be a quite simple piece of code.
in appendix operating time you can dynamically change pictures
Use td.setDrawableByLayerId(td.getId(1), drawable) on your TransitionDrawable
TransitionDrawable transitionDrawable = (TransitionDrawable) myImage
.getDrawable();
transitionDrawable.setDrawableByLayerId(transitionDrawable.getId(1), getResources()
.getDrawable(R.drawable.c));
You can only transition maximum two images with TransitionDrawable. To work with more than two images you can extend LayerDrawable and implement your own TransitionDrawable.
Here's the ready implementation of custom TransitionDrawable to work with more than two images.
You can see the complete sample along with the gif demo here on Github.
No need to use handler, it's more efficient + cleaner to use coroutines instead of a handler and as a bonus you can make this transition between as many drawables as you like:
/**
* Run multitransition animation on a view.
*
* #param drawableIds The drawables to load into the view, one after the other
* #param delayBetweenTransitions the number of milliseconds to transition from one drawable to the next
* #param viewToAnimate The view to animate.
*/
fun runMultitransitionAnimation(#DrawableRes vararg drawableIds: Int,
delayBetweenTransitions : Int,
viewToAnimate : View) {
CoroutineScope(Dispatchers.Main).launch(Dispatchers.Main.immediate) {
val delay = SOME_INTEGER_MILLISECONDS
for (i in 0 until drawableIds.size-1) {
val nextTransition = TransitionDrawable(arrayOf(
context.getDrawable(drawableIds[i]),
context.getDrawable(drawableIds[i+1])))
))
nextTransition.isCrossFadeEnabled = true
viewToAnimate.background = nextTransition
nextTransition.startTransition(delayBetweenTransitions)
delay(delayBetweenTransitions*2)
}
}
}
You'll need to just make a new TransitionDrawable per iteration.
This will step up through your index of all the images you add, you can have any number of images...
private Handler handler = new Handler();
private void startImageCrossfading(#IdRes int imageViewResId, #DrawableRes int... drawableIds) {
Drawable[] bitmapDrawables = new Drawable[drawableIds.length];
for (int i = 0; i < drawableIds.length; i++) {
// if you are using Vectors cross fade won't work unless you do this! - See my answer at https://stackoverflow.com/a/54583929/114549
// bitmapDrawables[i] = getBitmapDrawableFromVectorDrawable(this, drawableIds[i]);
bitmapDrawables[i] = ContextCompat.getDrawable(this, drawableIds[i]);
}
final ImageView imageView = findViewById(imageViewResId);
handler.postDelayed(new Runnable() {
int index = 0;
#Override
public void run() {
TransitionDrawable td = new TransitionDrawable(new Drawable[]{
bitmapDrawables[index % bitmapDrawables.length],
bitmapDrawables[++index % bitmapDrawables.length]
});
td.setCrossFadeEnabled(true);
imageView.setImageDrawable(td);
td.startTransition(500); // transitionDurationMillis
handler.postDelayed(this, 3000);
}
}, 3000);
}
On pause you should clean up the handler
handler.removeCallbacksAndMessages(null);
I have an onClick method set in the xml layout file that triggers the vibration of the phone for 100ms at this point I have the ImageView Visibility set to visible so it can be seen. I want the ImageView to be set back to gone again when the vibration has stopped. How do I go about this?
You can start this method at the same time:
public void timerDelayRemoveView(float time, final ImageView v) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
v.setVisibility(View.GONE);
}
}, time);
}