I am working on an Android Kotlin project. I am applying animation on views. Starting from the basics, I am trying to animate an image view from the bottom of the screen to the center of the screen.
I have an XML layout with the following code.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
android:background="#color/colorPrimaryDark"
tools:context=".MainActivity">
<LinearLayout
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/main_image_logo"
android:src="#drawable/memento_text_logo"
android:layout_width="#dimen/main_logo_image_width"
android:layout_height="wrap_content" />
<TextView
android:textColor="#android:color/white"
android:id="#+id/main_tv_slogan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/main_slogan"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
I am animating the logo image translating from the bottom to the center (where it is originally) in the activity with the following code.
private fun animateMainLogo() {
val valueAnimator = ValueAnimator.ofFloat(0f, main_image_logo.y)
valueAnimator.addUpdateListener {
val value = it.animatedValue as Float
main_image_logo.translationY = value
}
valueAnimator.interpolator = LinearInterpolator()
valueAnimator.duration = 1000
valueAnimator.start()
}
When I run the code, it is not animating the view. It is just there where it is and static. What is wrong with my code and how can I fix it?
translationY of view in layout is 0. If you want to animate it from bottom to current position - you should change translationY values from some positive value to 0.
private fun animateLogo() {
val translationYFrom = 400f
val translationYTo = 0f
val valueAnimator = ValueAnimator.ofFloat(translationYFrom, translationYTo).apply {
interpolator = LinearInterpolator()
duration = 1000
}
valueAnimator.addUpdateListener {
val value = it.animatedValue as Float
main_image_logo?.translationY = value
}
valueAnimator.start()
}
Same thing can be done this way:
private fun animateLogo() {
main_image_logo.translationY = 400f
main_image_logo.animate()
.translationY(0f)
.setInterpolator(LinearInterpolator())
.setStartDelay(1000)
.start()
}
Add this lines to LinearLayout and ConstraintLayout because without them LinearLayout will cut of parts of animated view when it is outside of LinearLayout bounds.
android:clipChildren="false"
android:clipToPadding="false"
Or make main_image_logo direct child of root ConstraintLayout. Here is result:
Related
How to hide image on top using material animations?
I can rotate image or change "X" "Y" but how can I do this task?
I guess you need to used clip. For example this file name is clipper.xml
<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:clipOrientation="vertical"
android:drawable="#drawable/dog"
android:gravity="bottom" />
Your image layout would contain this ImageView
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/imageView"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="#drawable/clipper"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
And in you fragment you can use this snippet:
val animator = ObjectAnimator.ofInt(0, 10000)
animator.apply {
interpolator = LinearInterpolator()
duration = 1000
}
animator.addUpdateListener {
val animProgress = it.animatedValue as Int
binding.imageView.drawable.level = 10000 - animProgress
}
animator.start()
I have a drawable that's being displayed in a layout. The drawable has default properties such as color and width. I'm creating a data class to be able to update those when the recycler view is created. The part that fills in the color in the middle is working. But I'm stuck on how to actually update the drawable width and the ImageView margins from the viewHolder.
mydrawable.xml
The layout file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/rect_outline"
android:src="#drawable/mydrawable"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</LinearLayout>
The data class
data class ImageData(
val c: String
val width: Int
val marginLeft : Int
)
MyViewHoler.kt
internal class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: ImageData) {
itemView.rect_outline.setColorFilter(Color.parseColor(item.c))
}
}
first put this into your xml image view component
android:scaleType="fitCenter"
my sugest is use scale with animation to get a good looking for the user
imageView.animate()
.scaleX(scaleValue)
.scaleY(scaleValue)
.setDuration(MOVING_ANIMATION_DURATION)
.start()
imageView.invalidate()
but you can set a new layout params like:
val l = imageView.layoutParams
l.height = value
l.width = value
imageView.layoutParams = l
you can do this way too
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
params.setMargins(0, 20, 0, 40)
params.height = 1
params. width = 1
imageView.layoutParams = params
I have one view "whiteCircle" inside a button which I want to move from it's initial position to the white box "imageSquared" describing an arc and then get back to its initial position.
What I have tried is:
private fun startArcAnimation() {
val path = Path()
val location = IntArray(2)
imageSquared.getLocationOnScreen(location)
path.arcTo(
0f,
0f,
location[0].toFloat() + imageSquared.width,
location[0].toFloat() + imageSquared.height,
180f,
180f,
true
)
val animator = ObjectAnimator.ofFloat(whiteCircle, View.X, View.Y, path)
animator.duration = 1000
animator.start()
}
And this is the result:
Can you help me setting path values correct?
I have been struggling with arcTo properties without success.
Thanks in advance.
Here is my implementation:
arcTo method create oval which is placed into rect. So first of all you need to create correct rect. Your path should start from 180 degree in oval and move 180 degrees clockwise (Zero angle of oval is in right side).
I suggest to animate translationX and translationY properties of view.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
greenButton.setOnClickListener {
startArcAnimation()
}
}
private fun startArcAnimation() {
if (red.translationX != 0f) {
//return to start position animation
red.animate().translationX(0f).translationY(0f).start()
return
}
val rectHeight = 600
val left = 0f
val top = -rectHeight / 2
val right = white.x - red.x
val bottom = white.y + rectHeight / 2 - red.y
val path = Path()
path.arcTo(left, top.toFloat(), right, bottom, 180f, 180f, true)
val animator = ObjectAnimator.ofFloat(red, View.TRANSLATION_X, View.TRANSLATION_Y, path)
animator.duration = 1000
animator.start()
}
}
Here is activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000"
android:padding="16dp">
<Button
android:id="#+id/greenButton"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="#0a0"
android:stateListAnimator="#null" />
<ImageView
android:id="#+id/white"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center_vertical|right"
android:background="#fff" />
<ImageView
android:id="#+id/red"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="70dp"
android:layout_marginBottom="10dp"
android:background="#f00" />
</FrameLayout>
I've got a CoordinatorLayout, including a LinearLayout as my BottomSheet. There is also a FrameLayout (direct child of the CoordinatorLayout as well).
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
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">
<FrameLayout
android:id="#+id/sketchViewGroup"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="#+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="340dp"
android:background="#drawable/rounded_top"
android:backgroundTint="#color/colorBottomSheetBackground"
android:orientation="vertical"
app:behavior_hideable="false"
app:behavior_peekHeight="#dimen/bottomSheetPeekHeight"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="#dimen/bottomSheetPeekHeight"
android:gravity="center_vertical"
android:orientation="horizontal">
<SeekBar
android:id="#+id/fixedPointsSeekBar"
style="#style/Widget.AppCompat.SeekBar.Discrete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:max="10"
android:progress="10" />
</LinearLayout>
</LinearLayout>
I use the Processing.jar libraries to draw stuff on the FrameLayout.
In order to do this I created a Fragment (so called PFragement in Processing) and added it to the FrameLayout via the SupportFragmentManager. The PFragment takes a PApplet as a constructor. In the PApplet the canvas drawing takes place and also the width and the height get defined. Furthermore I sized the PApplet to fill my FrameLayout.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sketch = Sketch();
sketchViewGroup.post {
sketch.sketchWidth = sketchViewGroup.width
sketch.sketchHeight = sketchViewGroup.height
// this works:
// sketch.sketchWidth = sketchViewGroup.width -1
// sketch.sketchHeight = sketchViewGroup.height -1
supportFragmentManager.beginTransaction().add(sketchViewGroup.id, PFragment(sketch)).commit();
}
}
}
and
public class Sketch extends PApplet {
int sketchWidth = 100;
int sketchHeight = 100;
public void settings() {
size(sketchWidth, sketchHeight);
}
public void setup(){
background(0);
}
}
Now, wired things are happening: When I size the Sketch to exactly the size of my FrameLayout my BottomSheet does not expand properly anymore. But if I subtract one of the size of my sketch everything works as expected. Can someone explain this and tell how to fix this?
sketch.sketchWidth = sketchViewGroup.width
sketch.sketchHeight = sketchViewGroup.height
sketch.sketchWidth = sketchViewGroup.width -1
sketch.sketchHeight = sketchViewGroup.height -1
It looks like the height of your view group is MATCH_PARENT which is -1. When you subtract 1 from that you get -2 which equals WRAP_CONTENT. So I think you are just setting the height of the sketch object to be WRAP_CONTENT?
I'm building a UI that consists of multiple CardViews inside a RecyclerView.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
android:id="#+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardCornerRadius="5dp"
app:cardElevation="1dp"
app:cardBackgroundColor="#ddffca"
android:animateLayoutChanges="true"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:padding="10dp"
>
<TextView
android:id="#+id/tvHello"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="Hello there!"
/>
<TextView
android:id="#+id/test"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:visibility="gone"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tvHello"
android:text="GENERAL KENOBI!"
/>
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>
Inside I have specified that on click even I will have my test text animated to appear. It's working great and I'm mostly happy with the results. But as soon as I add a few cards inside the RecyclerView the animation starts working a bit strange. What I mean is views that are not touched are not animating properly considering the other views have changed their size. Instead I see another view jumping to its new position without any animation.
How can I make it animate according to other views?
EDIT
I have also provided my code from onBindViewHolder. It's in Kotlin though:
override fun onBindViewHolder(
holder: OperationsViewHolder,
position: Int
) {
var card: CardView = holder.cardView
card.setOnClickListener {
if (!operations.get(position).selected!!) {
ObjectAnimator.ofFloat(card, "translationZ", 1f, 10f)
.start()
holder.test.visibility = View.VISIBLE;
operations.get(position)
.selected = true
} else {
ObjectAnimator.ofFloat(card, "translationZ", 10f, 1f)
.start()
holder.test.visibility = View.GONE;
operations.get(position)
.selected = false
}
}
}
EDIT 2 I have also tried adding android:animateLayoutChanges="true" to all elements, didn't help
Not sure if this fits the constraints of your question but you don't necessarily have to animate the expanding/collapsing manually. The RecyclerView can provide that to you out-of-the-box by using notifyItemChanged() properly in your Adapter.
override fun onBindViewHolder(
holder: OperationsViewHolder,
position: Int
) {
var card: CardView = holder.cardView
if (operations.get(position).selected!!) {
holder.test.visibility = View.VISIBLE;
} else {
holder.test.visibility = View.GONE;
}
card.setOnClickListener {
if (!operations.get(position).selected!!) {
operations.get(position)
.selected = true
} else {
operations.get(position)
.selected = false
}
notifyItemChanged(position)
}
}
The above code removes the animation logic and instead calls notifyItemChanged every time the CardView is clicked. This tells the RecyclerView to re-render the item at that particular position and gives you an animation for the re-render for free.