Android TransitionDrawable with multiple items - java

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);

Related

Android Spring Button

How to achieve springs effect on button in Android Studio. So that it decreases and increases when you push it. I have done many searches before posting this question, but could not get anything.
First, create a resource file of type animation. Then inside the xml add the following animation (spring.xml):
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<scale
android:duration="2500"
android:fromXScale="0.5"
android:toXScale="1.0"
android:fromYScale="0.5"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"/>
</set>
this will make the button go from half size to full size in 2,5 seconds. If you want a spring effect you'll need a helper class. For many of my programs I've used the following:
class MyInterpolator implements android.view.animation.Interpolator {
private double mAmplitude = 1;
private double mFrequency = 10;
MyBounceInterpolator(double amplitude, double frequency) {
mAmplitude = amplitude;
mFrequency = frequency;
}
public float getInterpolation(float time) {
return (float) (-1 * Math.pow(Math.E, -time/ mAmplitude) *
Math.cos(mFrequency * time) + 1);
}
}
if you want to set the animation programmatically use the following method on your button:
public void springView(View view) {
Button button = (Button)findViewById(R.id.button);
final Animation myAnimation = AnimationUtils.loadAnimation(this, R.anim.spring);
MyInterpolator interpolator = new MyInterpolator(0.1, 10);
myAnimation.setInterpolator(interpolator);
button.startAnimation(myAnimation);
}
Depending on how spring-y you want that button, increase the values inside the new MyInterpolator(0.1, 10).

Android: checking a view's background

I have 3 buttons, all of them have the same background drawable by default (subject_button)
What I want to do:
When I click one button his background changes ( to clicked_subject), all the others remain with the default background, if I click one button after clicking another, the button I just clicked changes his background while the previous one gets back to the initial background, allowing only one button to have the clicked_subject background, if the diffrent button gets clicked again his background goes back to the initial one, leting all the buttons with the initial background.
The problem:
If I click the diffrent button, his background remains the same instead of changing back to the initial one.
My logic:
theButton1.setBackgroundResource(R.drawable.subject_button);
theButton1.setOnClickListener(this);
//same for other 2
#Override
public void onClick(View v) {
if (v.getBackground() == ContextCompat.getDrawable(this, R.drawable.subject_button)) {
theButton1.setBackgroundResource(R.drawable.subject_button);
theButton2.setBackgroundResource(R.drawable.subject_button);
theButton3.setBackgroundResource(R.drawable.subject_button);
v.setBackgroundResource(R.drawable.clicked_subject);
} else {
v.setBackgroundResource(R.drawable.subject_button);
}
Why is this happening?
You can do this:
private final List<Button> mButtons = new ArrayList<>();
// somewhere in your code
mButtons.add(mButton1);
mButtons.add(mButton2);
mButtons.add(mButton3);
mButton1.setOnClickListener(mClickListener);
mButton2.setOnClickListener(mClickListener);
mButton3.setOnClickListener(mClickListener);
private final View.OnClickListener mClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
for (Button button : mButtons) {
if (button == v) {
// set selected background
} else {
// set not selected backround
}
}
}
};
If you define a stateful drawable for your buttons, then you can simply change the onclick to this:
private final View.OnClickListener mClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
for (Button button : mButtons) {
button.setSelected(v == button);
}
}
};
For changing background color/image based on the particular event(focus, press, normal), you need to define a button selector file and implement it as background for button. For example button_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="#drawable/your_image1" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="#drawable/your_image2" /> <!-- focused -->
android:drawable="#drawable/your_image3" <!-- default -->
</selector>
then just apply it as :
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawable="#drawable/button_selector.xml" />
I solved it by instead of changing only the background color on clicking the button, I also change the textColor, then I can check the textColor with if (((Button) v).getCurrentTextColor() == Color.WHITE)
"Pressed", "selected", "disabled" and such are View states. As such they're supposed to be handled automatically by Android and not by your click listeners.
This is achieved using SateLists, which control the look your Views sould have depending on which state(s) they're in. So you can easily set subject_button as the unpressed state, clicked_subject as the pressed state, and let Android take care of actually switching between them.
Full explanation: https://developer.android.com/guide/topics/resources/drawable-resource.html#StateList

How to adjust size of second image from setImageResource()

I am new to java/android and am making a test app. It has ImageButtons that when clicked switch to a different image temporarily. The originals are cropped using
android:adjustViewBounds="true"
android:scaleType="centerCrop"
in the activity_main.xml
The problem is the second image isnt cropped and is therefore too big for the button. DOes anyone know how I can fix this? HEres a an example of one of the buttons:
public void onClick(View v) {
//switch to second img
butt2.setImageResource(R.drawable.newimg);
//switch back to first after pause
new Handler().postDelayed(new Runnable() {
public void run() {
butt2.setImageResource(R.drawable.orig);
}
}, 500L);
}
});
I have used the following
int normal[] = { R.drawable.num0, R.drawable.num1, R.drawable.num2,
R.drawable.num3, R.drawable.num4, R.drawable.num5,
R.drawable.num6, R.drawable.num7, R.drawable.num8,
R.drawable.num9, R.drawable.del, R.drawable.go };
int pressed[] = { R.drawable.num0_clicked, R.drawable.num1_clicked,
R.drawable.num2_clicked, R.drawable.num3_clicked,
R.drawable.num4_clicked, R.drawable.num5_clicked,
R.drawable.num6_clicked, R.drawable.num7_clicked,
R.drawable.num8_clicked, R.drawable.num9_clicked,
R.drawable.del_clicked, R.drawable.go_clicked };
StateListDrawable sld;
for (int i = 0; i < 12; i++) {
sld = new StateListDrawable();
sld.addState(new int[] { android.R.attr.state_pressed },
getResources().getDrawable(pressed[i]));
sld.addState(new int[] {}, getResources().getDrawable(normal[i]));
btns[i].setImageDrawable(sld);
}
There's another way to attempt this thing , if you want like when you click the button its background should change and when you release it , background gets to previous one .
This can be achieved as follows
Now in your main.xml file that you want to show as an activity and where you have declared the button tag , add a statement
android:background="#drawable/customDefaultBackground"
and it will be like
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/customDefaultBackground" />
Now add an xml file of selector tag type and name it as customDefaultBackground.xml, open that file and add following to its contents :-
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_pressed="true" android:drawable="#drawable/onPressBackground" ></item>
<item android:drawable="#drawable/defaultBackground"></item>
</selector>
Item tag with android:state_pressed set to true , denotes that when a user clicks on a button and keep it pressed the backgroundOnPressed version will be displayed else it will show default background.

Android - Loading images with a delay

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.

Android Changing Seekbar Thumb with Java causes Thumb position to Reset

I am trying to create a seekbar with a thumb that changes color when it is pressed by the user and change back when the user lets go. I managed to change the thumb drawable using set thumb. However, I also had to set the drawable boundries in java to get it to appear after the change. Now when the user lets go of the slider and it reverts to its original drawable it resets the drawable to the zero position and leaves the progress where it should be. I have tried using a global variable to reset the seekbar progress after the drawable is changed, but it doesn't seems to work. If I use a static integer to reset the progress it works the way I want it to.
Kinda new to android and I don't know what I am doing wrong.
This is the code that changes the drawable when it is touched.
private void Seek_Height_Click() {
Drawable myThumb = getResources().getDrawable(R.drawable.slider_button_click);
myThumb.setBounds(new Rect(0, 0, myThumb.getIntrinsicWidth(),myThumb.getIntrinsicHeight()));
Height_of_Target_skbr.setThumb(myThumb);
Height_of_Target_skbr.setThumbOffset(-1);
}
This is the code to set the thumb back to the original drawable.
private void Seek_Height_UnClick() {
Drawable myThumb = getResources().getDrawable(R.drawable.slider_button);
myThumb.setBounds(new Rect(0, 0, myThumb.getIntrinsicWidth(),myThumb.getIntrinsicHeight()));
Height_of_Target_skbr.setThumb(myThumb);
Height_of_Target_skbr.setThumbOffset(-1);
Height_of_Target_skbr.setProgress(Height_Prog_int);
}
This is the code that calls the above method.
this.Height_of_Target_skbr.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
returnHeight_skbr();
Seek_Height_UnClick();
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
returnHeight_skbr();
Seek_Height_Click();
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
returnHeight_skbr();
}
});
I was able to make this work using by creating a style in the drawable folder and then setting the thumb drawable to that style.
I refereed to this tutorial to make this work.
http://www.youtube.com/watch?v=zXXCFmfJMNw
Code for Thumb Style
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:state_pressed="true"
android:drawable="#drawable/slider_button_click">
</item>
<item
android:drawable="#drawable/slider_button">
</item>
</selector>

Categories