Animatedly reduce button size on press and regain it's size on release
Asked Answered
R

9

15

I want a to create a button that changes size (little bit smaller) when it's pressed, and after the button is released again the size should change back to normal size.I am using Scale xml to achieve this but it re-positioned itself even if i do not release button.

button here i am referring to imageview.

here is my source code :-

imgSpin = (ImageView) findViewById(R.id.iv_spins);
    imgSpin.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    imgSpin.startAnimation(mAnimation);
                    spinslot();
                    break;

                case MotionEvent.ACTION_UP:
                    imgSpin.clearAnimation();
                    imgSpin.setEnabled(false);
                    break;
                }
                return true;
            }
        });

my scale Xml :-

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200"
    android:fromXScale=".8"
    android:fromYScale=".8"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1.0"
    android:toYScale="1.0" >

</scale>

my XML:-

 <LinearLayout
                android:id="@+id/layout_status"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight=".2"
                android:orientation="horizontal" >

                <View
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight=".5" />

                <ImageView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="2.1"
                    android:background="@drawable/bootser" />

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="2.5"
                    android:background="@drawable/bet_bg"
                    android:gravity="center_vertical"
                    android:orientation="horizontal"
                    android:weightSum="1" >

                    <ImageView
                        android:layout_width="0dp"
                        android:layout_height="match_parent"
                        android:layout_gravity="center_vertical"
                        android:layout_weight=".2"
                        android:src="@drawable/leftarrow" />

                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="1dp"
                        android:layout_weight=".6"
                        android:gravity="center"
                        android:text="Bet"
                        android:textColor="@android:color/white" />

                    <ImageView
                        android:layout_width="0dp"
                        android:layout_height="40dp"
                        android:layout_gravity="center_vertical"
                        android:layout_weight=".2"
                        android:src="@drawable/rightarrow" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="2.5"
                    android:background="@drawable/container"
                    android:gravity="center" >

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="2dp"
                        android:text="winning"
                        android:textColor="@android:color/white" />
                </LinearLayout>

                <ImageView
                    android:id="@+id/iv_spins"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1.9"
                    android:background="@drawable/spin" />

                <View
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight=".5" />
            </LinearLayout>

I tried with paramLayout but as i set the weights in XML so it do not give me the effect on animation.

i already use this approach :-

case MotionEvent.ACTION_DOWN:
                ViewGroup.LayoutParams params = imgSpin.getLayoutParams();
                // Button new width
                params.width = params.width - (params.width *90/100);

                imgSpin.setLayoutParams(params);

                break;

params.width always return me zero so i tried to do this with weight , by using weight my view glitch on click ..i want it to be smooth so i am preferring scaling

Second approach :-

case MotionEvent.ACTION_DOWN:
                LinearLayout.LayoutParams params = (android.widget.LinearLayout.LayoutParams) imgSpin
                        .getLayoutParams();
                // Button new width
                params.weight = 1.7f;

                imgSpin.setLayoutParams(params);
                // imgSpin.startAnimation(mAnimation);
                // spinslot();
                break;

            case MotionEvent.ACTION_UP:
                LinearLayout.LayoutParams params2 = (android.widget.LinearLayout.LayoutParams) imgSpin
                        .getLayoutParams();
                // Button new width
                params2.weight = 1.9f;

                imgSpin.setLayoutParams(params2);
                // imgSpin.clearAnimation();
                imgSpin.setEnabled(false);
                break;
            }
            return true;

it works but not smooth

Please tell me how to do

Thanks

Reggy answered 19/5, 2016 at 8:42 Comment(0)
S
20

Here is what you want :-

imgSpin.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    ObjectAnimator scaleDownX = ObjectAnimator.ofFloat(imgSpin,
                            "scaleX", 0.8f);
                    ObjectAnimator scaleDownY = ObjectAnimator.ofFloat(imgSpin,
                            "scaleY", 0.8f);
                    scaleDownX.setDuration(1000);
                    scaleDownY.setDuration(1000);

                    AnimatorSet scaleDown = new AnimatorSet();
                    scaleDown.play(scaleDownX).with(scaleDownY);

                    scaleDown.start();

                    spinslot();
                    break;

                case MotionEvent.ACTION_UP:
                    ObjectAnimator scaleDownX2 = ObjectAnimator.ofFloat(
                            imgSpin, "scaleX", 1f);
                    ObjectAnimator scaleDownY2 = ObjectAnimator.ofFloat(
                            imgSpin, "scaleY", 1f);
                    scaleDownX2.setDuration(1000);
                    scaleDownY2.setDuration(1000);

                    AnimatorSet scaleDown2 = new AnimatorSet();
                    scaleDown2.play(scaleDownX2).with(scaleDownY2);

                    scaleDown2.start();

                    imgSpin.setEnabled(false);
                    break;
                }
                return true;
            }
        });

And just make this few changes in your xml :-

<ImageView
                    android:id="@+id/iv_spins"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1.9"
                    android:adjustViewBounds="true"
                    android:background="@drawable/spin"
                    android:scaleX="1"
                    android:scaleY="1" />

make use of scaleDown.play(scaleDownX).with(scaleDownY),it will keep the animated position and will not restore it back to original

Hope it solves your problem cheers

Starchy answered 19/5, 2016 at 9:59 Comment(0)
S
11
  1. Create two XMLs in /res/animator

    reduce_size.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set android:ordering="sequentially"
        xmlns:android="http://schemas.android.com/apk/res/android">
        <set>
            <objectAnimator
                android:propertyName="scaleX"
                android:duration="300"
                android:valueTo="0.9f"
                android:valueType="floatType"/>
            <objectAnimator
                android:propertyName="scaleY"
                android:duration="300"
                android:valueTo="0.9f"
                android:valueType="floatType"/>
        </set>
    </set>
    

    regain_size.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set android:ordering="sequentially"
        xmlns:android="http://schemas.android.com/apk/res/android">
        <set>
            <objectAnimator
                android:propertyName="scaleX"
                android:duration="1000"
                android:valueTo="1f"
                android:startOffset="300"
                android:valueType="floatType"/>
            <objectAnimator
                android:propertyName="scaleY"
                android:duration="1000"
                android:valueTo="1f"
                android:startOffset="300"
                android:valueType="floatType"/>
        </set>
    </set>
    
  2. Usage

    On MotionEvent.ACTION_DOWN

    AnimatorSet reducer = (AnimatorSet) AnimatorInflater.loadAnimator(mContext,R.animator.squeeze_in);
    reducer.setTarget(view);
    reducer.start();
    

    On MotionEvent.ACTION_UP

    AnimatorSet regainer = (AnimatorSet) AnimatorInflater.loadAnimator(mContext,R.animator.squeeze_out);
    regainer.setTarget(view);
    regainer.start();
    
Superman answered 28/12, 2016 at 16:3 Comment(0)
K
8

Since API 21 (Lollipop) an xml-only solution is available based on the stateListAnimator attribute. Here's an example of a layout which scale to 0.9 of its original size when it's pressed:

<!-- res/layout/my_layout.xml -->

<LinearLayout ...
  android:stateListAnimator="@animator/zoom_out_animator">...</layout>
<!-- res/animator/zoom_out_animator -->

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item>
    <set>
      <objectAnimator
          android:propertyName="scaleX"
          android:valueTo="1.0"
          android:duration="@android:integer/config_shortAnimTime"
          android:interpolator="@android:interpolator/fast_out_slow_in"/>
      <objectAnimator
          android:propertyName="scaleY"
          android:valueTo="1.0"
          android:duration="@android:integer/config_shortAnimTime"
          android:interpolator="@android:interpolator/fast_out_slow_in"/>
    </set>
  </item>
  <item android:state_pressed="true">
    <set>
      <objectAnimator
          android:propertyName="scaleX"
          android:valueTo="0.9"
          android:duration="@android:integer/config_shortAnimTime"
          android:interpolator="@android:interpolator/fast_out_slow_in"/>
      <objectAnimator
          android:propertyName="scaleY"
          android:valueTo="0.9"
          android:duration="@android:integer/config_shortAnimTime"
          android:interpolator="@android:interpolator/fast_out_slow_in"/>
    </set>
  </item>
</selector>

You can also find an example in the official material sample app Owl. Take a look at onboarding_topic_item.xml.

A few similar questions:

Ketchum answered 19/11, 2020 at 12:16 Comment(1)
FYI, one problem with this solution is the order within the set. The pressed state needs to be listed BEFORE the default item. If it's not in the right order the animation will not workGarboil
A
2

You can do this programmatically as follows on the touchlistener

new View.OnTouchListener() {
  public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction == MotionEvent.ACTION_DOWN) {
      // scale your value
      float reducedvalue = (float)0.7;
      v.setScaleX(reducedvalue);
      v.setScaleY(reducedvalue);
    }
    else if (event.getAction == MotionEvent.ACTION_UP) {
      v.setScaleX(1);
      v.setScaleY(1);
    }
  } 
}
Amoreta answered 27/3, 2018 at 7:58 Comment(0)
M
2

It's very simple but you have to know how the Android API handles the call of each listener.

Here is the trick:

1- onTouch method in TouchListener will be called first.
1.1- If onTouch method returns true, so onLongClick(in LongClickListener) or onClick(in ClickListener) will be ignored(both will not be called any more).
2- If onTouch method returns false, then check if onLongClick returns true so onClick will not be called but if it returns false so onClick will be called as well.

Here is an example:

targetButton.setOnLongClickListener(v -> {
        targetButton.animate().scaleX(1.2f).scaleY(1.2f).setDuration(0);
        Log.d("Button", "onLong: ");
        return true;
    });
    targetButton.setOnClickListener(v -> {
        Log.d("Button", "onClick: ");
    });
    targetButton.setOnTouchListener((v, event) -> {
        Log.d("Button", "onTouch: ");
        if (event.getAction() == MotionEvent.ACTION_UP) {
            recordButton.animate().scaleX(1.0f).scaleY(1.0f).setDuration(0);
        } 
          // if you want to make it pretty increase the size when click also
          /* else if (event.getAction() == MotionEvent.ACTION_DOWN) {
            targetButton.animate().scaleX(1.2f).scaleY(1.2f).setDuration(0);
            } */
        return false;
    });

In this example I am trying to increase the size of the button if I detect a long press, otherwise do something into onClick method, and if the user release the button I reduce the size again.

If you want the onClick called every time the onLongClick called just make the onLongClick returns false;

Magulac answered 17/9, 2019 at 8:11 Comment(0)
M
1

You can add fillafter property: android:fillAfter="true"

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200"
    android:fromXScale=".8"
    android:fromYScale=".8"
    android:pivotX="50%"
    android:pivotY="50%"
    android:fillAfter="true"
    android:toXScale="1.0"
    android:toYScale="1.0" >
</scale>
Microscopium answered 19/5, 2016 at 9:22 Comment(0)
B
0
<ImageView
  android:id="@+id/iv_spins"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:layout_weight="1.9"
  android:adjustViewBounds="true"
  android:background="@drawable/spin"
  android:scaleX="1"
  android:scaleY="1" 
/>
Bidget answered 21/10, 2016 at 6:33 Comment(2)
It's better to add explanation. What's your code/post intended to do. And don't use HTML code snippets for Android XML layout codes.Adler
@Adler I removed the snippet and set it to a code block, still it needs an edit from rohit Shewale for more explanation.Thaddeusthaddus
M
0

Kotlin Solution

  1. Create extension function:
    fun View.addElasticTouchEffect() {
        this.setOnTouchListener { view, motionEvent ->
            when (motionEvent.action) {
                MotionEvent.ACTION_DOWN -> {
                    this.animate().scaleX(0.96f).scaleY(0.96f).setDuration(100).start()
                }
                MotionEvent.ACTION_UP,
                MotionEvent.ACTION_CANCEL -> {
                    this.animate().scaleX(1f).scaleY(1f).setDuration(100).start()
                    if (motionEvent.action == MotionEvent.ACTION_UP) {
                        this.performClick()
                    }
                }
            }
            return@setOnTouchListener true
        }
    }
    
  2. Call created function in view:
    class MainActivity : AppCompatActivity() {
        private lateinit var binding: ActivityMainBinding
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
            binding.ivAddress.addElasticTouchEffect()
            binding.ivAddress.setOnClickListener {
                // on click
            }
        }
    }
    

Hope this helped you

Margie answered 28/3, 2023 at 11:58 Comment(0)
T
0

Here's the code that worked for me:

<!--res/animator/scale_pressed.xml-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
    <set>
        <objectAnimator
            android:propertyName="scaleX"
            android:valueTo="1.0"
            android:duration="@android:integer/config_shortAnimTime"
            android:interpolator="@android:interpolator/fast_out_slow_in"/>
        <objectAnimator
            android:propertyName="scaleY"
            android:valueTo="1.0"
            android:duration="@android:integer/config_shortAnimTime"
            android:interpolator="@android:interpolator/fast_out_slow_in"/>
    </set>
</item>
<item android:state_pressed="true">
    <set>
        <objectAnimator
            android:propertyName="scaleX"
            android:valueTo="0.90"
            android:duration="@android:integer/config_shortAnimTime"
            android:interpolator="@android:interpolator/fast_out_slow_in"/>
        <objectAnimator
            android:propertyName="scaleY"
            android:valueTo="0.90"
            android:duration="@android:integer/config_shortAnimTime"
            android:interpolator="@android:interpolator/fast_out_slow_in"/>
    </set>
</item>
</selector>

Basically it's the same as in Valeriy Katkov's answer, but I added android:state_pressed="false" without which it didn't work in my case.

And you can use it like that:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    android:stateListAnimator="@animator/scale_pressed"/>

Here's how it looks:

GIF

Tigress answered 28/8, 2023 at 21:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.