I am trying to animate a vector path to a different path in my android app for testing but its not working properly . no animation is displayed on screen and neither any animation is shown.
My vector file is:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="5dp"
android:viewportWidth="8"
android:viewportHeight="5">
<path
android:name="redot"
android:pathData="M2.5,2.5L6,2.5"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#E61B1B"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>
And my VectorAnimation file is:
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:targetApi="lollipop"
android:drawable="#drawable/ic_reddot">
<target
android:animation="#anim/redanim"
android:name="redot"/>
</animated-vector>
My Animation file in anim folder is:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="10000"
android:propertyName="pathData"
android:valueFrom="M2.5,2.5L6,2.5"
android:valueTo="M2.5,2.5L31,2.5"
android:valueType="pathType" />
</set>
And finally my MainActivityCode is as following:
public class MainActivity extends AppCompatActivity {
private TextView testObj;
private ImageView reddot;
private AnimatedVectorDrawable animation;
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// testObj = (TextView) findViewById(R.id.testObj);
// testObj.setVisibility(View.INVISIBLE);
reddot = (ImageView) findViewById(R.id.reddot);
Drawable d = reddot.getBackground();
if (d instanceof AnimatedVectorDrawable) {
Log.d("testanim", "onCreate: instancefound" );
animation = (AnimatedVectorDrawable) d;
animation.start();
}
}
}
Use Shape Shifter tool and than export the animated vector drawable file generated by shape shifter . add this file in your drawable folder and than add this as background to your imageview which you want to animate
my avd_anim.xml file:
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="80dp"
android:height="12dp"
android:viewportWidth="80"
android:viewportHeight="12">
<path
android:name="path"
android:pathData="M 6 6 L 74 6"
android:strokeColor="#e61b1b"
android:strokeWidth="12"
android:strokeLineCap="round"
android:fillType="evenOdd"/>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="pathData"
android:duration="1000"
android:valueFrom="M 6 6 L 74 6"
android:valueTo="M 6 6 L 1 6"
android:valueType="pathType"
android:interpolator="#android:anim/linear_interpolator"/>
</aapt:attr>
</target>
My activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="#+id/object"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:src="#drawable/avd_anim"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
and finally my MainActivity.class
public class MainActivity extends AppCompatActivity {
private ImageView image;
private AnimatedVectorDrawable animation;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
image = (ImageView) findViewById(R.id.object);
}
#Override
protected void onStart() {
super.onStart();
Drawable d = image.getDrawable();
if (d instanceof AnimatedVectorDrawable) {
Log.d("testanim", "onCreate: instancefound" + d.toString());
animation = (AnimatedVectorDrawable) d;
animation.start();
}
}
}
You should use redot.getDrawable() instead of getBackground()
And if you are using app:srcCompat="#drawable/..." instead of android:src=#drawable/... for ImageView you should add
if (d instanceof AnimatedVectorDrawableCompat) {
AnimatedVectorDrawableCompat avd = (AnimatedVectorDrawableCompat) d;
avd.start();
}
And android:animation="#anim/redanim" should be android:animation="#animator/redanim". Use animator folder
Related
I would like to create an Animation with 3 dots (one Is bigger than the other two and have different color), this animation should loop at start center one should be bigger after that dot on the right should be bigger and change color.
I created this XML:
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:android="http://schemas.android.com/apk/res/android">
<aapt:attr name="android:drawable">
<vector
android:width="300dp"
android:height="80dp"
android:viewportWidth="300"
android:viewportHeight="80">
<path
android:name="left_selected"
android:pathData="M30,44m-30,0a30,30 0,1 1,60 0a30,30 0,1 1,-60 0"
android:fillColor="#CBA818"/>
<path
android:name="center"
android:pathData="M150,40m-40,-0a40,40 0,1 0,80 -0a40,40 0,1 0,-80 -0"
android:fillColor="#757575"/>
<path
android:name="right"
android:pathData="M270,44m-30,-0a30,30 0,1 0,60 -0a30,30 0,1 0,-60 -0"
android:fillColor="#757575"/>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="217"
android:interpolator="#android:interpolator/fast_out_slow_in"
android:propertyName="pathData"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="..."
android:valueTo=..."
android:valueType="pathType" />
<objectAnimator
android:duration="144"
android:interpolator="#android:interpolator/fast_out_slow_in"
android:propertyName="pathData"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:startOffset="217"
android:valueFrom="..."
android:valueTo="..."
android:valueType="pathType" />
<objectAnimator
android:duration="239"
android:interpolator="#android:interpolator/fast_out_slow_in"
android:propertyName="pathData"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:startOffset="361"
android:valueFrom="..."
android:valueTo="..."
android:valueType="pathType" />
</set>
</aapt:attr>
</target>
</animated-vector>
How could I change a color of dot in animation tag? Is my way of thinking correct with this approach?
You can try this approach
XML:-
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/clCenterArrows"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="#id/ivImage"
app:layout_constraintEnd_toEndOf="#id/ivImage"
app:layout_constraintStart_toStartOf="#id/ivImage"
app:layout_constraintTop_toTopOf="#id/ivImage">
<ImageView
android:id="#+id/ivArrows"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_tag_instruction_arrows"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/vTagCenter"
android:layout_width="#dimen/d12"
android:layout_height="#dimen/d12"
android:background="#drawable/rounded_fill_red"
app:layout_constraintBottom_toBottomOf="#id/ivArrows"
app:layout_constraintEnd_toEndOf="#id/ivArrows"
app:layout_constraintStart_toStartOf="#id/ivArrows"
app:layout_constraintTop_toTopOf="#id/ivArrows" />
<View
android:id="#+id/vAnim1"
android:layout_width="#dimen/d12"
android:layout_height="#dimen/d12"
android:background="#drawable/rounded_fill_red"
app:layout_constraintBottom_toBottomOf="#id/ivArrows"
app:layout_constraintEnd_toEndOf="#id/ivArrows"
app:layout_constraintStart_toStartOf="#id/ivArrows"
app:layout_constraintTop_toTopOf="#id/ivArrows" />
<View
android:id="#+id/vAnim2"
android:layout_width="#dimen/d12"
android:layout_height="#dimen/d12"
android:background="#drawable/rounded_fill_red"
app:layout_constraintBottom_toBottomOf="#id/ivArrows"
app:layout_constraintEnd_toEndOf="#id/ivArrows"
app:layout_constraintStart_toStartOf="#id/ivArrows"
app:layout_constraintTop_toTopOf="#id/ivArrows" />
</androidx.constraintlayout.widget.ConstraintLayout>
Kotlin:-
val runnableAnim = object : Runnable {
override fun run() {
vAnim1?.visibility = View.VISIBLE
vAnim2?.visibility = View.VISIBLE
vAnim1
?.animate()
?.scaleX(3f)
?.scaleY(3f)
?.alpha(0f)
?.setDuration(1000)
?.withEndAction {
kotlin.run {
vAnim1?.scaleX = 1f
vAnim1?.scaleY = 1f
vAnim1?.alpha = 1f
}
}
vAnim2
?.animate()
?.scaleX(3f)
?.scaleY(3f)
?.alpha(0f)
?.setDuration(700)
?.withEndAction {
kotlin.run {
vAnim2?.scaleX = 1f
vAnim2?.scaleY = 1f
vAnim2?.alpha = 1f
}
}
animationHandler.postDelayed(this, 1500)
}
}
runnableAnim.run()
This is the output of the animation
I have the following ImageView
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/image_header_toggle"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="10dp"
app:layout_constraintBottom_toBottomOf="#+id/text_header_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/text_header_name"
android:src="#drawable/ic_header_off_to_on"
app:tint="?attr/colorPrimary"/>
and two animated drawables that I made through https://shapeshifter.design/
ic_header_off_to_on.xml
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="group"
android:pivotX="12"
android:pivotY="12">
<path
android:name="path_1"
android:pathData="M 15.41 16.58 L 10.83 12 L 15.41 7.41 L 14 6 L 8 12 L 14 18 L 15.41 16.58 Z"
android:fillColor="#000"
android:strokeWidth="1"/>
</group>
</vector>
</aapt:attr>
<target android:name="group">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="rotation"
android:duration="300"
android:valueFrom="0"
android:valueTo="-90"
android:valueType="floatType"
android:interpolator="#android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>
ic_header_on_to_off.xml
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="group"
android:pivotX="12"
android:pivotY="12"
android:rotation="270">
<path
android:name="path_1"
android:pathData="M 15.41 16.58 L 10.83 12 L 15.41 7.41 L 14 6 L 8 12 L 14 18 L 15.41 16.58 Z"
android:fillColor="#000"
android:strokeWidth="1"/>
</group>
</vector>
</aapt:attr>
<target android:name="group">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="rotation"
android:duration="300"
android:valueFrom="270"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="#android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>
and this code that I use to toggle the icons
val res = if (isExpanded) R.drawable.ic_header_off_to_on else R.drawable.ic_header_on_to_off
image_header_toggle.setImageResource(res)
(view.image_header_toggle.drawable as? AnimatedVectorDrawable)?.start()
image_header_toggle.setOnClickListener { isExpanded = !isExpanded }
The off_to_on animation seems to play nicely but the on_to_off one doesn't, the image simply gets replace.
It looks like this >_<
I just found this question and tried something out. A more elegant solution is to remove your Kotlin code with mine. This will achieve a smooth transition and you even can change the duration of it. Instead of needing 2 drawables you can just use one. If you are using Fragment instead of Activity, don't forget to set view.findViewById instead of findViewById:
class MainActivity : AppCompatActivity() {
private var isExpanded = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val imageView = findViewById<ImageView>(R.id.image_header_toggle)
imageView.setOnClickListener {
if (!isExpanded) {
imageView.animate().apply {
duration = 500
rotation(0f)
isExpanded = true}
}else{
imageView.animate().apply {
duration = 500
rotation(-90f)
isExpanded = false}
}
}
}
}
First of all, please don't declare it as duplicate because i have been spending time on stackoverflow since last five days and i have read a lot of answers but still not able to achieve this.
I want card flip animation in my application. I tried https://developer.android.com/training/animation/cardflip.html also but exit animation is not being played there for me.
So i used two frame layouts for two fragments in a Linear Layout and then tried to rotate and translate first frame layout towards left out of screen while rotating and translating next frame layout in from right.First frame is rotating as required but translation is not there.
Please help to make frame layout translate also with rotation so that next fragment could enter the screen.
My code is:
activity_card.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/toolbar"
android:background="#color/colorPrimary"
>
</android.support.v7.widget.Toolbar>
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="horizontal"
>
<FrameLayout
android:id="#+id/frame_layout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
>
</FrameLayout>
<FrameLayout
android:id="#+id/frame_layout2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
>
</FrameLayout>
</LinearLayout>
</LinearLayout>
card_flip_left_out.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Rotate. -->
<objectAnimator
android:valueFrom="0"
android:valueTo="180"
android:propertyName="rotationY"
android:interpolator="#android:interpolator/accelerate_decelerate"
android:startOffset="0"
android:duration="1000" />
<!-- Half-way through the rotation (see startOffset), set the alpha to 0. -->
<objectAnimator
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha"
android:startOffset="0"
android:duration="800" />
<objectAnimator
android:propertyName="translationX"
android:valueTo="-200"
android:interpolator="#android:interpolator/accelerate_decelerate"
android:startOffset="0"
android:duration="1000"
/>
</set>
card_flip_right_in.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Before rotating, immediately set the alpha to 0. -->
<objectAnimator
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha"
android:duration="0" />
<!-- Rotate. -->
<objectAnimator
android:valueFrom="180"
android:valueTo="0"
android:propertyName="rotationY"
android:interpolator="#android:interpolator/accelerate_decelerate"
android:duration="1000" />
<!-- Half-way through the rotation (see startOffset), set the alpha to 1. -->
<objectAnimator
android:valueFrom="0.0"
android:valueTo="1.0"
android:propertyName="alpha"
android:startOffset="0"
android:duration="800" />
<objectAnimator
android:propertyName="translationX"
android:valueTo="0"
android:interpolator="#android:interpolator/accelerate_decelerate"
android:startOffset="0"
android:duration="1000"
/>
</set>
CardActivity.java:
public class CardActivity extends AppCompatActivity implements
Toolbar.OnMenuItemClickListener {
FragmentTransaction fragmentTransaction;
FrameLayout layout1, layout2;
String visibleFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_card);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle("Digital card");
toolbar.setTitleTextColor(Color.WHITE);
toolbar.inflateMenu(R.menu.menu);
toolbar.setOnMenuItemClickListener(this);
layout1 = (FrameLayout) findViewById(R.id.frame_layout1);
layout2 = (FrameLayout) findViewById(R.id.frame_layout2);
fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame_layout1, GeneralDetailsFragment.newInstance());
fragmentTransaction.replace(R.id.frame_layout2, AddMoreDetailsFragment.newInstance());
visibleFragment = "layout1";
fragmentTransaction.commit();
}
#Override
public boolean onMenuItemClick(MenuItem item) {
Animator anim1 = AnimatorInflater.loadAnimator(this,R.animator.card_flip_left_out);
Animator anim2 = AnimatorInflater.loadAnimator(this,R.animator.card_flip_right_in);
if(visibleFragment.equals("layout2"))
{
anim1.setTarget(layout2);
anim2.setTarget(layout1);
anim1.start();
anim2.start();
visibleFragment = "layout1";
}
else
{
anim1.setTarget(layout1);
anim2.setTarget(layout2);
anim1.start();
anim2.start();
visibleFragment = "layout2";
}
return true;
}
}
Try this way:
below is xml file for animation (left to right with rotation) and start animation on view. And make changes as per you need.
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/linear_interpolator">
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="5"
android:toDegrees="359" />
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0%p" //For Y - android:fromYDelta="0%p" also -value
android:toXDelta="100%p" //For Y - android:toYDelta="50%p"
android:repeatCount="0"
android:duration="3000">
</translate>
</set>
Hope this will help you.
I found a different and pretty simple way to achieve card flip animation here . This animation is not for replacing or adding fragment using FragmentTransaction but for two frame layouts which already contain corresponding Fragment.
Sample code is:
public void flip(final View front, final View back, final int duration) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
AnimatorSet set = new AnimatorSet();
set.playSequentially(
ObjectAnimator.ofFloat(front, "rotationY", 90).setDuration(duration / 2),
ObjectAnimator.ofInt(front, "visibility", View.GONE).setDuration(0),
ObjectAnimator.ofFloat(back, "rotationY", -90).setDuration(0),
ObjectAnimator.ofInt(back, "visibility", View.VISIBLE).setDuration(0),
ObjectAnimator.ofFloat(back, "rotationY", 0).setDuration(duration / 2)
);
set.start();
}
else {
front.animate().rotationY(90).setDuration(duration / 2).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
front.setVisibility(View.GONE);
back.setRotationY(-90);
back.setVisibility(View.VISIBLE);
back.animate().rotationY(0).setDuration(duration / 2).setListener(null);
}
});
}
}
I am trying to achieve the morphing effect, when the user clicks myButton, the image inside the ImageView should morph from arrow to checkmark. And when he clicks it again, the process should reverse: the checkmark should morph to arrow.
This is what I have done:
animated_vector.xml:
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/vector_upvote">
<target
android:name="rotationGroup"
android:animation="#anim/rotate_a_to_b" />
<target
android:name="upvote"
android:animation="#animator/animator_upvote_to_checkmark" />
</animated-vector>
animator_upvote_to_checkmark.xml:
<objectAnimator
android:duration="250"
android:propertyName="pathData"
android:valueFrom="#string/svg_upvote"
android:valueTo="#string/svg_checkmark"
android:valueType="pathType"
android:repeatMode="reverse" />
And this is how I play the animation:
Drawable drawable = upVote.getDrawable();
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
}
This morphs the arrow into checkmark, how do I reverse the process?
if your view is checkable and also your minsdk > 21 you can try using StateListAnimator with AnimatedVectorDrawable but because the appcompat now supports vectorDrawable and AnimatedVectorDrawable and also has a limitation on using all DrawableContainers I do not take above approach.
So let me tell you what may work:
DrawableContainers which reference other drawables resources which
contain only a vector resource. For example, a StateListDrawable which
references other files which contain a vector.
from Chris Banes
In order to achieve that I am going to create custom ImageView and every time it is clicked you must call morph function to run appropriate vectorAnimatorDrawable.
So code and demo:
public class ArrowToCheckMarkMorphingImageView extends ImageView {
private AnimatedVectorDrawable mArrowToCheckMark;
private AnimatedVectorDrawable mCheckMarkToArrow;
private boolean mIsShowingCheckMark;
public ArrowToCheckMarkMorphingImageView(Context context) {
super(context);
init();
}
public ArrowToCheckMarkMorphingImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ArrowToCheckMarkMorphingImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public ArrowToCheckMarkMorphingImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
public void init(){
mIsShowingCheckMark = false;
mArrowToCheckMark =
(AnimatedVectorDrawable)getContext().getDrawable(R.drawable.arrow_to_checkmark);
mCheckMarkToArrow =
(AnimatedVectorDrawable)getContext().getDrawable(R.drawable.checkmark_to_arrow);
setImageDrawable(mArrowToCheckMark);
}
public void morph(){
final AnimatedVectorDrawable drawable
= mIsShowingCheckMark ? mCheckMarkToArrow : mArrowToCheckMark;
setImageDrawable(drawable);
drawable.start();
mIsShowingCheckMark = !mIsShowingCheckMark;
}
}
and MainActivity:
public class MainActivity extends AppCompatActivity {
ArrowToCheckMarkMorphingImageView mArrowToCheckMarkImageView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mArrowToCheckMarkImageView = (ArrowToCheckMarkMorphingImageView)findViewById(R.id.imageView);
mArrowToCheckMarkImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mArrowToCheckMarkImageView.morph();
}
});
}
}
in layout file you must use it like:
<yourpackage.ArrowToCheckMarkMorphingImageView
android:id="#+id/imageView"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
/>
and resources for those who wants to give it a try:
res/animator/arrow_to_checkmark
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="500"
android:propertyName="pathData"
android:valueFrom="M7.41,15.41 L12,10.83 l4.59,4.58 L18,14 18,14 l-6,-6 -6,6z"
android:valueTo ="M9,16.17 L4.83,12 l-1.42,1.41 L9,19 21,7 l-1.41,-1.41 0,0z"
android:valueType="pathType" />
<objectAnimator
android:duration="500"
android:propertyName="fillColor"
android:valueFrom="#FF0000FF"
android:valueTo="#FF00FF00" />
</set>
res/animator/checkmark_to_arrow
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="500"
android:propertyName="pathData"
android:valueFrom="M9,16.17 L4.83,12 l-1.42,1.41 L9,19 21,7 l-1.41,-1.41 0,0z"
android:valueTo ="M7.41,15.41 L12,10.83 l4.59,4.58 L18,14 18,14 l-6,-6 -6,6z"
android:valueType="pathType" />
<objectAnimator
android:duration="500"
android:propertyName="fillColor"
android:valueFrom="#FF00FF00"
android:valueTo="#FF0000FF" />
</set>
res/drawable/arrow_to_checkmark
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/ic_arrow_up_24dp">
<target
android:animation="#animator/arrow_to_checkmark"
android:name="arrow"/>
</animated-vector>
res/drawable/checkmark_to_arrow
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/ic_checkmark_24dp">
<target
android:animation="#animator/checkmark_to_arrow"
android:name="checkmark"/>
</animated-vector>
res/drawable/ic_arrow_up_24dp
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:name="arrow"
android:fillColor="#FF0000FF"
android:pathData="M7.41,15.41 L12,10.83 l4.59,4.58 L18,14 18,14 l-6,-6 -6,6z"/>
</vector>
res/drawable/ic_checkmark_24dp
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:name="checkmark"
android:fillColor="#FF00FF00"
android:pathData="M9,16.17 L4.83,12 l-1.42,1.41 L9,19 21,7 l-1.41,-1.41 0,0z"/>
</vector>
I will go with Circular Progress Button, you can customize it based on your need or extend the library (since. it's open source). I have been using that for a while and it is pretty easy to integrate on your projects.
Also, you can learn from that and create your own library :-)
Here the Git repository
CircularProgressButton
Also, you can try out other excellent open source such as Android Morphing Button
I am currently working on an Android app in which I use a FloatingActionButton. I would like to use the speed dial to have multiple actions that spin/jump out of the action button as described in this page by Google on Android design, or as could be seen in an earlier version of the Keep app (sorry, but I can only post one link). I am using the Android Design Support library specifically version 23.1.1 (com.android.support:design:23.1.1). I already searched using Google and looked at the reference for the FloatingActionButton but couldn't find anything concerning the speed dial.
I would like to know if there is a way to easily achieve this using the default FloatingActionButton, or if I have to program all transitions/animations manually?
Additionally I would like to have little labels next to the buttons, describing the action, if possible.
Thank you in advance!
I'm here to add my 2 cents because this is where I landed after Googling for that exact title.
I hope it doesn't come too late to help someone like me.
First off, the solution comes from here, so is not mine. I just tried and it works nicely. So i thought i share with you in a single post rather have you go dig the code up from there.
The solution uses com.android.support:design:25.3.1 library so be sure to add that to build.gradle and it requires API 21 onwards.
The bad news is that it is composed from several small moving parts: 5 animators, 5 drawables plus the icons and layouts and of course, the code, the good news is that it works as it should, is highly customizable and doesn't require any coding outside MainActivity.
Some notes:
The big fab's image morphs between more and minus signs and rotates when tapped.
Buttons can have text, provided you put both the text and each small fab inside a LineaLayout and move the button id to the LinearLayout so it gets animated instead of the fab, but it requires code to hide and show the text when necessary.
This is the result:
So, the ingredients:
Drawables (res/drawable/).
animated_minus.xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
android:height="24dp">
<group android:name="plus_group" android:pivotX="12" android:pivotY="12">
<path
android:name="plus_path"
android:strokeColor="#android:color/white"
android:strokeWidth="3"
android:pathData="M12,0L12,24M0,12,L24,12" />
</group>
</vector>
animated_plus.xml
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/plus">
<target
android:animation="#animator/rotate_clockwise"
android:name="plus_group" />
<target
android:animation="#animator/plus_to_minus"
android:name="plus_path" />
</animated-vector>
fab_background.xml
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item>
<shape android:shape="oval">
<solid android:color="?android:colorAccent" />
</shape>
</item>
</ripple>
minus.xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
android:height="24dp">
<group android:name="plus_group" android:pivotX="12" android:pivotY="12">
<path
android:name="plus_path"
android:strokeColor="#android:color/white"
android:strokeWidth="3"
android:pathData="M12,12L12,12M0,12,L24,12" />
</group>
</vector>
plus.xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
android:height="24dp">
<group android:name="plus_group" android:pivotX="12" android:pivotY="12">
<path
android:name="plus_path"
android:strokeColor="#android:color/white"
android:strokeWidth="3"
android:pathData="M12,0L12,24M0,12,L24,12" />
</group>
</vector>
Animators (res/animator/).
fab_state_list_animator.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:state_enabled="true">
<set>
<objectAnimator
android:propertyName="translationZ"
android:duration="100"
android:valueTo="3dp"
android:valueType="floatType" />
<objectAnimator
android:propertyName="elevation"
android:duration="0"
android:valueTo="5dp"
android:valueType="floatType" />
</set>
</item>
<!-- base state -->
<item android:state_enabled="true">
<set>
<objectAnimator
android:propertyName="translationZ"
android:duration="100"
android:valueTo="0"
android:startDelay="100"
android:valueType="floatType" />
<objectAnimator
android:propertyName="elevation"
android:duration="0"
android:valueTo="5dp"
android:valueType="floatType" />
</set>
</item>
<item>
<set>
<objectAnimator
android:propertyName="translationZ"
android:duration="0"
android:valueTo="0"
android:valueType="floatType" />
<objectAnimator
android:propertyName="elevation"
android:duration="0"
android:valueTo="0"
android:valueType="floatType" />
</set>
</item>
</selector>
minus_to_plus.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="pathData"
android:valueFrom="M12,0L12,24M12,12,L12,12"
android:valueTo="M12,0L12,24M0,12,L24,12"
android:valueType="pathType"
android:duration="#android:integer/config_mediumAnimTime" />
plus_to_minus.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="pathData"
android:valueFrom="M12,0L12,24M0,12,L24,12"
android:valueTo="M12,0L12,24M12,12,L12,12"
android:valueType="pathType"
android:duration="#android:integer/config_mediumAnimTime" />
rotate_anticlockwise.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="rotation"
android:valueFrom="90"
android:valueTo="0"
android:valueType="floatType"
android:duration="#android:integer/config_mediumAnimTime" />
rotate_clockwise.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="90"
android:valueType="floatType"
android:duration="#android:integer/config_mediumAnimTime" />
Layouts. (res/layout/)
fab.xml. All fabs are declared here. Replace android:src with your own icon on the first 3 ImageButtons.
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:id="#+id/fab_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="#dimen/activity_vertical_margin"
android:layout_marginEnd="#dimen/activity_horizontal_margin"
android:clipChildren="false" >
<!-- Please note that the #id are defined the first time they're referenced from top to bottom -->
<ImageButton
android:id="#+id/fab_action_3"
style="#style/FloatingActionButton.Mini"
android:src="#drawable/ic_volume_up_white_24dp"
android:layout_above="#+id/fab_action_2"
android:layout_alignEnd="#+id/fab"
android:contentDescription="#null"
android:backgroundTint="#color/sa_gray"
android:width="24dp"
android:height="24dp"
android:onClick="fabAction3" />
<ImageButton
android:id="#id/fab_action_2"
style="#style/FloatingActionButton.Mini"
android:src="#drawable/ic_credit_card_white_24dp"
android:layout_above="#+id/fab_action_1"
android:layout_alignEnd="#id/fab"
android:contentDescription="#null"
android:backgroundTint="#color/sa_gray"
android:width="24dp"
android:height="24dp"
android:onClick="fabAction2" />
<ImageButton
android:id="#id/fab_action_1"
style="#style/FloatingActionButton.Mini"
android:src="#drawable/ic_add_shopping_cart_white_24dp"
android:layout_above="#id/fab"
android:layout_alignEnd="#id/fab"
android:contentDescription="#null"
android:backgroundTint="#color/sa_gray"
android:width="24dp"
android:height="24dp"
android:onClick="fabAction1" />
<ImageButton
android:id="#id/fab"
style="#style/FloatingActionButton"
android:src="#mipmap/ic_add_w"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:contentDescription="#null"
android:visibility="visible"
android:layout_marginTop="8dp" />
</RelativeLayout>
</merge>
And lastly.
The code (java//MainActivity.java)
a) Some declarations:
private static final String TAG = "Floating Action Button";
private static final String TRANSLATION_Y = "translationY";
private ImageButton fab;
private boolean expanded = false;
private View fabAction1;
private View fabAction2;
private View fabAction3;
private float offset1;
private float offset2;
private float offset3;
b) Delete the usual fab code on MainActivity's onCreate:
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
and replace it with:
final ViewGroup fabContainer = (ViewGroup) findViewById(R.id.fab_container);
fab = (ImageButton) findViewById(R.id.fab);
fabAction1 = findViewById(R.id.fab_action_1);
// insert onClickListener here
fabAction2 = findViewById(R.id.fab_action_2);
// insert onClickListener here
fabAction3 = findViewById(R.id.fab_action_3);
// insert onClickListener here
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
expanded = !expanded;
if (expanded) {
expandFab();
} else {
collapseFab();
}
}
});
fabContainer.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
fabContainer.getViewTreeObserver().removeOnPreDrawListener(this);
offset1 = fab.getY() - fabAction1.getY();
fabAction1.setTranslationY(offset1);
offset2 = fab.getY() - fabAction2.getY();
fabAction2.setTranslationY(offset2);
offset3 = fab.getY() - fabAction3.getY();
fabAction3.setTranslationY(offset3);
return true;
}
});
c) Add supporting functions on MainActivity (animation code mostly and the 3 small fab's onClick methods):
private void collapseFab() {
fab.setImageResource(R.drawable.animated_minus);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(createCollapseAnimator(fabAction1, offset1),
createCollapseAnimator(fabAction2, offset2),
createCollapseAnimator(fabAction3, offset3));
animatorSet.start();
animateFab();
}
private void expandFab() {
fab.setImageResource(R.drawable.animated_plus);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(createExpandAnimator(fabAction1, offset1),
createExpandAnimator(fabAction2, offset2),
createExpandAnimator(fabAction3, offset3));
animatorSet.start();
animateFab();
}
private Animator createCollapseAnimator(View view, float offset) {
return ObjectAnimator.ofFloat(view, TRANSLATION_Y, 0, offset)
.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
}
private Animator createExpandAnimator(View view, float offset) {
return ObjectAnimator.ofFloat(view, TRANSLATION_Y, offset, 0)
.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
}
private void animateFab() {
Drawable drawable = fab.getDrawable();
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
}
}
public void fabAction1(View view) {
Log.d(TAG, "Action 1");
Toast.makeText(this, "Go shopping!", Toast.LENGTH_SHORT).show();
}
public void fabAction2(View view) {
Log.d(TAG, "Action 2");
Toast.makeText(this, "Gimme money!", Toast.LENGTH_SHORT).show();
}
public void fabAction3(View view) {
Log.d(TAG, "Action 3");
Toast.makeText(this, "Turn it up!", Toast.LENGTH_SHORT).show();
}
d) Reference the fab.xml layout from res/layout/activity_main.xml
Delete the fab declaration:
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
app:srcCompat="#android:drawable/ic_dialog_email" />
Replace with:
<include layout="#layout/fab" />
Last notes:
Feel free to scrap the onClick code for the fabs and replace it with
onClickListener. Those should go right where the comment that says
// insert onClickListener here. Just remember to delete the
onClick attribute for each fab in fab.xml file and get rid of
the last 3 functions in MainActivity (fabAction1, fabAction2
and fabAction3).
Most measures, dimensions, etc. I put them right in the code to avoid including even more files.
The code is not optimized on changed in any way.
I hope this helps someone and sorry for the wall of text.
I would like to know if there is a way to easily achieve this using the default FloatingActionButton
FAB from Design Library does not have this feature. You need to look for 3rd party FABs (there's a few on android-arsenal to choose from)
This library is implementing the Speed Dial from the Material Design guidelines:
https://github.com/leinardi/FloatingActionButtonSpeedDial