Android Spring Button - java

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

Related

Crash when using Handler/addView in Android Studio

so basically i have 2 problems.. 1st problem is the handler and runnable..
i have an image and i want to make it move..
so i wrote this
ImageView a = findViewById(R.id.os);
final Handler handler = new Handler();
final Runnable run = new Runnable() {
#Override
public void run() {
float y = a.getY();
if(y > layout.getHeight())
y=0;
else
y+=7;
handler.postDelayed(this,3000);
a.setY(y);
}
};
handler.post(run);
when i run it, the app crashes...my layout is ConstraintLayout.
and i wrote this code in a method "onClick" so after clicking the button the image starts moving.
..
2nd Problem : i want to add alot of objects (enemies etc to make a game) but the problem if I do that using XML it will take a long time. imagine adding 200 objects and what if I wanted to repeat adding them after they get removed or changing their attributes ? i will need to define an id for all them and create objects in reference to their id's all of them ...so I needed to figure out how to add objects programmatically to the ConstraintLayout..so i Wrote this:
ArrayList<ImageView> bosses=new ArrayList<>();
ConstraintLayout d = findViewById(R.id.layout);
for(int i =0;i<200;i++) {
bosses.add(new ImageView(this));
ConstraintLayout.LayoutParams lay =
new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
lay.startToStart= d.getId(); lay.endToStart= d.getId();
lay.topToTop = d.getId(); lay.bottomToTop=d.getId();
bosses.get(i).setLayoutParams(lay);
d.addView(bosses.get(i));
}
and it crashes too....I'm trying to learn how to move objects and how to add them programmatically so I can create games but i keep facing these crashes. if anyone could help I'll be thankful.

LayoutParams won't properly put ImageViews next to each other

Currently, I'm trying to make an infinitely long chain of ImageViews that are all next to each other.
I attempt to do this by using addRule and LayoutParam so that every new ImageView has to be set to the right of the ImageView before it.
However, I encounter an odd problem. When I do run my code, this is the result :
Not only are my two ImageViews not even next to each other, it doesn't work for any number past two! If I just create two blocks, it gives the result below.
If I make any more blocks than 2, they'll just stack on top of block #2. Which is what happens in the image above.
Also, I have to set every ImageView with its own ID because if I don't, they'll all have an ID of -1.
I've been stuck on this for like 5 hours in a row, and it's really starting to get annoying. (Please do not suggest that I use a LinearLayout. I already know, and I can't use it.)
public class TerrainFactory {
int count = 0;
int idCount = 1;
Terrain terrain;
public Terrain createNewTerrain(Activity activity, RelativeLayout relativeLayout,
final ArrayList<Terrain> terrainArrayList){
//TODO: Add the rectangle bounds into this.
terrain = new Terrain();
terrain.is = activity.getResources().openRawResource(+R.drawable.game_tile);
terrain.tile = BitmapFactory.decodeStream(terrain.is);
terrain.tileDrawable = new BitmapDrawable(activity.getResources(), terrain.tile);
terrain.terrainImage = new ImageView(activity);
terrain.terrainImage.setImageDrawable(terrain.tileDrawable);
//noinspection ResourceType
terrain.terrainImage.setId(idCount);
idCount++;
if(count >= 1) {
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);;
layoutParams.addRule(RelativeLayout.RIGHT_OF, terrainArrayList.get(count - 1).terrainImage.getId());
relativeLayout.addView(terrain.terrainImage, layoutParams);
terrain.terrainImage.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
//TODO: Set rectangle bounds in here
++count;
terrain.terrainImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}else{
++count;
relativeLayout.addView(terrain.terrainImage);
terrain.terrainImage.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
//TODO: Set rectangle bounds in here
terrain.terrainImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
return terrain;
}
public int getCount() { return count; }
}
Here is my PlayActivity class, where all the block creating happens :
public class PlayActivity extends Activity {
RelativeLayout relativeLayout;
TerrainFactory terrainFactory;
ArrayList<Terrain> terrainArrayList = new ArrayList<Terrain>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
relativeLayout = (RelativeLayout) findViewById(R.id.relativeLayout);
terrainFactory = new TerrainFactory();
for(int i = 0; i < 7; i++){
terrainArrayList.add(terrainFactory.createNewTerrain(PlayActivity.this, relativeLayout, terrainArrayList));
}
}
}
And here is the Terrain class
public class Terrain {
public Bitmap tile;
public InputStream is;
public ObjectAnimator moveLeft;
public Drawable tileDrawable;
public Rect bounds;
public ImageView terrainImage;
}
Here's the XML for the RelativeLayout. The RelativeLayout I'm using is the one nested inside the RelativeLayout that fills the entire screen.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.hooray.project.fun.activity.PlayActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="false"
android:id="#+id/relativeLayout"></RelativeLayout>
</RelativeLayout>
Basically I jsut would tackle this in another way....using not Views for buildiung terrains but using surfaceview and draw bitmaps on it...
But well lets do it your way:
Place your views into a FrameLayout. Then when you create your views give them their dimension in code and also set the x and y positions in code.
For example this is how I give a View the needed Dimension and the position in my Code. Its a Textview and I jsut set the x and y position which I have stuffed iinto variables so I can chnage them how i want:
tView.setLayoutParams(new FrameLayout.LayoutParams(hintListWidth,hintListHeight));
tView.setX(hintListPosX);
tView.setY(hintListPosY);
maybe for your example do it like this:
float startPosX=???;
float startPosY=???;
float i=0;
for(Terrian t:terrainArrayList)
{
tView.setLayoutParams(new FrameLayout.LayoutParams(width,height));
tView.setX(startPosX+width*i);
tView.setY(startPosY);
i++;
}
If you have the width and height ready and know where to start placing the first view every view will be placed side by side from left to right...
As suggested in the other answer(s) to your question, the problem is that counter on the if-true branch is never incremented before views are drawn (aka tree observer callback being called). You are calling addView but I think this goes on and adds the view asynchronously, while you go on and add views. So you finish adding the views and only after that they are drawn. I suggest taking the counter out of the callback and place it at the end of the if-true block.
Note: if you are targeting SDK17+ you can use View.generateViewId() instead of manually keeping track / incrementing it yourself. Not that it matters in this particular scenario, but it will not allow the generated id to conflict with aapt-generated ids and it will roll it over once the top limit is reached.

Set animation YDelta in Java class?

I am running an animation within my listview, and I wondering how I am able to set the xml attribute, yDelta, within my java class. It is contingent upon the size of the current row, so I can't use a single value. Here is my animation code
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="20%p"
android:toYDelta="20"
android:duration="#android:integer/config_mediumAnimTime"/>
<alpha android:fromAlpha="1.0" android:toAlpha="1.0"
android:duration="#android:integer/config_mediumAnimTime" />
here is my java code
public void updateDataList(ArrayList<HashMap<String, String>> newList){
final ArrayList<HashMap<String, String>> newListAdd = newList;
Animation anim = AnimationUtils.loadAnimation(
liveStreamFragment.getActivity(), R.anim.top_to_down
);
anim.setDuration(2000);
list.startAnimation(anim);
new Handler().postDelayed(new Runnable() {
public void run() {
oslist.clear();
oslist.addAll(newListAdd);
LiveAdapter.this.notifyDataSetChanged();
}
}, anim.getDuration());
}
Accordingly to this documentation you can set it in constructor:
TranslateAnimation translate = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
Also check this similar question.

Android ObjectAnimator fill after option?

In my android application I am using an ObjectAnimator to translate an imageview like so:
ObjectAnimator transitionX = ObjectAnimator.ofFloat(v, "TranslationY", 190);
Is there an option like fillAfter that will reset the position of the view to the new position, after the animation is complete?
Nope, I'd use the property animation system instead, assuming v is a view:
v.animate().translationY(190).withEndAction(new Runnable() {
#Override
public void run() {
v.setTranslationY(v.getTranslationY()-190);
}
}).start();
Nope, there is no option for that. However you can achieve the same effect using a listener where you can manually change it to the initial position in the onAnimationEnd(). Here is an example for scaling an imageview:
scaleDown.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
//Initial property values
tempIV.setScaleX(1f);
tempIV.setScaleY(1f);
tempIV.setAlpha(1f);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
I think the right way to handle this is to use an AnimationSet to merge two animations, one to translate and one to translate back the view.
Something like this
ObjectAnimator translateTo190= ObjectAnimator.ofFloat(v, "TranslationY", 190);
ObjectAnimator translateBack = ObjectAnimator.ofFloat(v, "TranslationY", 0);
AnimatorSet translate= new AnimatorSet();
translate.play(translateTo190).before(translateBack);
translate.start();
I have had a problem like this in writing a card dealing animation where the cards are dealt quickly from a central "deck". After much trial-and-error, the way I've got this working is using the ObjectAnimator in the following way.
I add a single "deck" card at the source of the animation, and then a second card that I'm actually animating. I use the following deal_from_drawpile.xml to animate the card in two parts: the first part returns the card to the source, and the second part scales and rotates the card as it is "dealt":
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/accelerate_interpolator"
android:ordering="sequentially">
<set android:interpolator="#android:anim/accelerate_interpolator"
android:ordering="together">
<objectAnimator
android:propertyName="translationX"
android:duration="10"
android:valueFrom="0.0f"
android:valueTo="0.0f"/>
<objectAnimator
android:propertyName="translationY"
android:duration="10"
android:valueFrom="0.0f"
android:valueTo="0.0f"/>
<objectAnimator
android:propertyName="scaleX"
android:duration="10"
android:valueFrom="0.75f"
android:valueTo="0.75f"/>
<objectAnimator
android:propertyName="scaleY"
android:duration="10"
android:valueFrom="0.75f"
android:valueTo="0.75f"/>
</set>
<set android:ordering="together">
<objectAnimator
android:propertyName="rotation"
android:duration="300"
android:valueFrom="0.0f"
android:valueTo="360.0f"/>
<objectAnimator
android:propertyName="scaleX"
android:duration="300"
android:valueFrom="0.75f"
android:valueTo="0.5f"/>
<objectAnimator
android:propertyName="scaleY"
android:duration="300"
android:valueFrom="0.75f"
android:valueTo="0.5f"/>
</set>
Then in code I add the actual translation which depends on how many hands are arranged in a rough semi-circle. The complete sequence of animations is then added in an AnimatorSet and fired off at the end. Code-snippet;
fullScreenContent.addView(dealtCardView);
for (int iCard=0; iCard<numCards; iCard++) {
for (int iPlayer = 0; iPlayer < numPlayers; iPlayer++) {
dealtCardAnimator = dealtCardAnimator.clone();
dealtCardAnimator.setTarget(dealtCardView);
//offsets where the cards are dealt according to player
ObjectAnimator playerOffsetXAnimator = ObjectAnimator.ofFloat(dealtCardView, "TranslationX", mGame.getPlayer(iPlayer).getPlayerLayout().getTranslationX());
ObjectAnimator playerOffsetYAnimator = ObjectAnimator.ofFloat(dealtCardView, "TranslationY", mGame.getPlayer(iPlayer).getPlayerLayout().getTranslationY());
if (lastDealtCardAnimator == null) dealSet.play(dealtCardAnimator).with(playerOffsetXAnimator).with(playerOffsetYAnimator);
else dealSet.play(dealtCardAnimator).with(playerOffsetXAnimator).with(playerOffsetYAnimator).after(lastDealtCardAnimator);
//The card is returned to the home point with the first portion of deal_from_drawpile
lastDealtCardAnimator = dealtCardAnimator;
}//end for iPlayer
}//end for numCards
dealSet.start();
I can now tinker with the duration of each step in the XML to get the effect I want. Note that you have to embed the duration in each element , not in the set section (where it is ignored, and some arbitrary default seems to be used instead).

Android TransitionDrawable with multiple items

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

Categories