I'm trying to animate an ImageView with a list of pictures, iterating through them quickly, so that
it creates the impression of an animation. Sadly it does give the alert: Skipped 391 frames! The application may be doing too much work on its main thread.
This is my Main-Activity which implements the Animator-interface.
override fun onCreate(savedInstanceState: Bundle?) {
GlobalScope.launch {
show(Action.Idle)
}
}
My Animator show() function (runs the Runnables with android.os.Handler):
fun show(action: Action){
getHandler().removeCallbacksAndMessages(null)
when (action) {
Action.MoveRight -> getHandler().post(getRight())
Action.MoveUp -> getHandler().post(getUp())
Action.MoveDown -> getHandler().post(getDown())
Action.MoveLeft -> getHandler().post(getLeft())
Action.Idle -> getHandler().post(idle())
}
}
My idle function (anim.idle is an array of Ints/Drawables):
fun idle() = object : Runnable{
override fun run() {
val anim = getAnimation()
repeat(anim.idle!!.size){
getActivity().runOnUiThread {
binding.figure.setImageResource(anim.idle[it])
binding.figure.invalidate()
}
Thread.sleep(500)
}
getHandler().postDelayed(this,5000)
}
}
You can use AnimationDrawable to have the ImageView show a sequence of pictures, see also the guide to Animate drawable graphics
In a nutshell, you declare a animation-list drawable resource and set it as the View background (or foreground, according to your requirements). You can then obtain the AnimationDrawable from the View at runtime and use it to start the animation.
In my small example, I used some vector drawables with different tints
to create the animation-list in res/drawable/colored_androids.xml:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:drawable="#drawable/ic_android_blue_24dp" android:duration="250"/>
<item android:drawable="#drawable/ic_android_mint_24dp" android:duration="250"/>
<item android:drawable="#drawable/ic_android_green_24dp" android:duration="250"/>
<item android:drawable="#drawable/ic_android_mint_24dp" android:duration="250"/>
</animation-list>
My Activity has a field for the AnimationDrawable
private lateinit var coloredAndroidsAnimation: AnimationDrawable
and in onCreate() the following lines are required for the setup:
findViewById<ImageView>(R.id.iv_animated).apply {
setBackgroundResource(R.drawable.colored_androids)
coloredAndroidsAnimation = background as AnimationDrawable
setOnClickListener{
coloredAndroidsAnimation.start()
}
}
While I don't think you can change a view that is attached to the UI from a background thread, you can create/edit views on a background thread (I do this lots)
So a possible solution might be to programmatically create a series of ImageViews in the background. It might then be possible to measure them with the same parameters as the UI in the background thread as measurement can be costly (not sure if they would be re-measured when added to the UI)
Then in the UI thread your loop would add and remove Views from the parent view.
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
i have one activity that has 3 text view. i want to animate these text views one by one.
first txt1 come from bottom to middle then txt2 come 10dp under txt1 and finally txt3.
please guide me how implement it
This can be achieved with translate and alpha animation.
Create XML file named down_to_top.xml in anim folder (create one in you "res" folder if it's not present)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="700">
<translate
android:fromYDelta="50%p"
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:toYDelta="0" />
<alpha
android:fromAlpha="0"
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:toAlpha="1" />
</set>
In your activity
Initialize your text views to tv1,tv2,tv3
Copy these three functions in your activity.
Call setAnimation() in your onCreate function of activity
private fun setAnimation() {
tv2.visibility = View.INVISIBLE // initially keeping t2 invisible
tv3.visibility = View.INVISIBLE // initially keeping t3 invisible
// Creating obect of Animation
val anim = AnimationUtils.loadAnimation(activity,
R.anim.down_to_up)
//Listener to get callback when animation of tv1 ends
val animListener1 = object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {}
override fun onAnimationEnd(animation: Animation) {
// when animation of first TextView ends we start
animation of textView 2 and make it visible
animateSecondView()
tv2.visibility = View.VISIBLE
}
override fun onAnimationRepeat(animation: Animation) {}
}
anim.setAnimationListener(animListener1)
tv1.startAnimation(anim)
}
private fun animateSecondView() {
val anim = AnimationUtils.loadAnimation(activity,
R.anim.down_to_up)
//Listener to get callback when animation of tv2 ends
val animListener2 = object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {}
override fun onAnimationEnd(animation: Animation) {
// when animation of TextView 2 ends we start animation of
textView 3 and make it visible
animateThirdView()
tv3.visibility = View.VISIBLE
}
override fun onAnimationRepeat(animation: Animation) {}
}
anim.setAnimationListener(animListener2)
tv2.startAnimation(anim)
}
private fun animateThirdView() {
val anim = AnimationUtils.loadAnimation(activity,R.anim.down_to_up)
tv3.startAnimation(anim)
}
You will have a result like this.
Here are a few ways you can achieve animations in android
Move a view using animation
A good place to start with the recommended way of writing animations in Android.
AndroidViewAnimations
A beginner-friendly pre-made animations library for Android
Animate all the things. Transitions in Android
A very good article explaining how to implement different types of translations on Views
Above mentioned links are a great place to get started.
I want to implement shared element transition in my app, when one activity's recycler view item transforms into another activity like here: https://storage.googleapis.com/spec-host-backup/mio-design%2Fassets%2F15N3n1xwTt0briEbfIvFUG01pMv2d_xaT%2F02-focus-focalelement-do.mp4. (source: https://material.io/design/motion/choreography.html#using-a-focal-element)
Namely, the item is fading out and changes bounds then the new activity fade in. As far as I understand it is simple AutoTransition, but it doesn't work. Simple fading doesn't work as well.
Thus, for now I achieve only that the item gets background of new activity an then changes its bounds.
So, I ended up by adding recycler view item's layout in the resulting activity layout. The data (e.g. the title, etc.) of the clicked item is transferred to the next activity with intent.putExtra(). Shared elements in such case will be of course the item's root view and resulting activity's root view. When activity starts I set the data of the item to matching views in activity via SharedElementCallback, e.g.:
setEnterSharedElementCallback(
object : SharedElementCallback() {
override fun onSharedElementStart(...) {
val title = intent.getStringExtra(TITLE)
activity_item_title.text = title
........
}
override fun onSharedElementEnd(...) {
}
})
This allows to show exactly the same item view at the beginning of the transition. Then it should start change its bounds, fading out this item's view at the same time. And at some moment (e.g. in the middle of the transition) when the initial view completely fades out, the laouyt of the activity shows up, fading in gradually. To do this we need to hide item's view in the middle of the transition (View.visibility = View.GONE) and make activity views visible. Probably this is not the best way, but I solve this by adding a listener to shared element enter transition and used Handler().postDelayed:
window.sharedElementEnterTransition.addListener(
object : android.transition.Transition.TransitionListener {
override fun onTransitionEnd(transition: Transition) {}
override fun onTransitionResume(transition: Transition) {}
override fun onTransitionPause(transition: Transition) {}
override fun onTransitionCancel(transition: Transition) {}
override fun onTransitionStart(transition: Transition) {
Handler().postDelayed({
activity_item.visibility = View.GONE
activity_view_1.visibility = View.VISIBLE
activity_view_2.visibility = View.VISIBLE
.............
.............
// Also you could e.g. set the background to your activity here, ets.
activity_view_root.background = backgroundDrawable
}, 150) //suppuse that the whole transition length is 300ms
}
}
})
The transition animations themselves could look as follows:
<transitionSet>
<targets>
<target android:targetId="#id/activity_root_view"/>
</targets>
<transition
class="com.organization.app.utils.FadeOutTransition"
android:duration="150"/>
<transition
class="com.organization.app.utils.FadeInTransition"
android:startDelay="150"/>
<changeBounds android:duration="300"/>
</transitionSet>
Here, custom FadeOutTransition and FadeInTransition were used since simple android <fade/> animation doesn't work with shared elements. These classes are similar to that given in the answer here: Why Fade transition doesn't work on Shared Element.
The steps for creating return transition are similar.
I've implemented shared element transitions for my app, where the transition begins on an image from a Fragment (with RecyclerView) inside a ViewPager on the home screen and expands into full screen gallery view, again within a Fragment in a ViewPager. This is all working fine except that if the image is not fully visible it goes on top of the TabBar before expanding into full screen. Here's what's happening:
My enter transition looks like this:
<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/android">
<targets>
<target android:excludeId="#android:id/statusBarBackground"/>
<target android:excludeId="#android:id/navigationBarBackground"/>
</targets>
</fade>
And exit:
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together"
android:duration="500">
<fade>
<targets>
<target android:excludeId="#android:id/statusBarBackground" />
<target android:excludeId="#android:id/navigationBarBackground" />
</targets>
</fade>
</transitionSet>
And in the shared element callback of the calling activity I've got this:
View navigationBar = activity.findViewById(android.R.id.navigationBarBackground);
View statusBar = activity.findViewById(android.R.id.statusBarBackground);
if (navigationBar != null) {
names.add(navigationBar.getTransitionName());
sharedElements.put(navigationBar.getTransitionName(), navigationBar);
}
if (statusBar != null) {
names.add(statusBar.getTransitionName());
sharedElements.put(statusBar.getTransitionName(), statusBar);
}
Finally in styles.xml for the activity theme:
<item name="android:windowContentTransitions">true</item>
<item name="android:windowEnterTransition">#transition/details_window_enter_transition</item>
<item name="android:windowReturnTransition">#transition/details_window_return_transition</item>
I don't really understand how the toolbar (or actionbar) can be excluded by the transition without getting this overlap. Perhaps a way to do it would be to somehow force the image to be clipped at the top part so that it doesn't become fully visible when under the ToolBar and expands only from the visible rectangle.
I've tried adding <target android:excludeId="#id/action_bar_container"/> to the targets of the animation but the same thing happens still.
Any suggestions are welcome.
I found a similar problem in my project.Add below code in your style.
<item name="android:windowSharedElementsUseOverlay">false</item>
It works for me.
I came up with a temporary workaround. Before the shared element transition is executed, the calling activity checks whether the target view is within the bounds of the RecyclerView. If not, the RecyclerView is smooth-scrolled to show the full view and once that is finished the transition runs. If the view is fully visible, then transitions runs normally.
// Scroll to view and run animation when scrolling completes.
recycler.smoothScrollToPosition(adapterPosition);
recycler.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recycler.getViewTreeObserver().removeOnPreDrawListener(this);
// Open activity here.
return true;
}
});
Here's the result, not too bad until I find a better solution:
I guess i have the proper answer for this question. This issue happening because on the second activity you do not have toolbar at all, so it can not be added to transition as shared element. So in my case i added some 'fake toolbar' to my second activity, which have 0dp height. Then you can add toolbar from first activity as a shared element, and give him change bounds animation, so toolbar will collapse at the same time as image and image will no longer be 'over' toolbar.
My 'fake toolbar' view:
<View
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#color/colorPrimary"
android:elevation="10dp"
android:outlineProvider="none"
android:transitionName="toolbar_transition" />
important notes:
-view have to have non-transparent background
-i added elevation to be sure that my view i 'over' image
-elevation causes shadow, whick i did not wanted, so i set outilenProvider as none
Next all you have to do is add your toolbar to shared elements
sharedElements.add(new Pair<>(toolbar, "toolbar_transition"));
I searched everywhere and couldn't find any solution so I figured myself. Here is a recorded demo (at half the speed) to show the result (with and without the fix).
Please checkout the full working demo here:
https://github.com/me-abhinav/shared-element-overlap-demo
Steps
Let us say we have two activities viz. MainActivity which has a scrolling container with a grid/list of thumbnails, and we have a SecondActivity which shows the image in a slideshow in fullscreen.
Please checkout the full code to completely understand the solution.
Inside your MainActivity which hosts the scrolling container, set a click listener on your thumbnail to open SecondActivity:
ImageView imageView = findViewById(R.id.image_view);
imageView.setOnClickListener(v -> {
// Set the transition name. We could also do it in the xml layout but this is to demo
// that we can choose any name generated dynamically.
String transitionName = getString(R.string.transition_name);
imageView.setTransitionName(transitionName);
// This part is important. We first need to clip this view to only its visible part.
// We will also clip the corresponding view in the SecondActivity using shared element
// callbacks.
Rect localVisibleRect = new Rect();
imageView.getLocalVisibleRect(localVisibleRect);
imageView.setClipBounds(localVisibleRect);
mClippedView = imageView;
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra(SecondActivity.EXTRA_TRANSITION_NAME, transitionName);
intent.putExtra(SecondActivity.EXTRA_CLIP_RECT, localVisibleRect);
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
MainActivity.this,
Pair.create(imageView, transitionName));
startActivity(intent, options.toBundle());
});
Restore the clip in onResume() of your MainActivity.
#Override
protected void onResume() {
super.onResume();
// This is also important. When we come back to this activity, we need to reset the clip.
if (mClippedView != null) {
mClippedView.setClipBounds(null);
}
}
Create transition resource in your res folder like this:
app/src/main/res/transition/shared_element_transition.xml
The contents should be similar to this:
<?xml version="1.0" encoding="utf-8"?>
<transitionSet
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="375"
android:interpolator="#android:interpolator/fast_out_slow_in"
android:transitionOrdering="together">
<!-- This is needed to clip the invisible part of the view being transitioned. Otherwise we
will see weird transitions when the image is partially hidden behind appbar or any other
view. -->
<changeClipBounds/>
<changeTransform/>
<changeBounds/>
</transitionSet>
Set the transition in your SecondActivity.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
// Setup transition
Transition transition =
TransitionInflater.from(this)
.inflateTransition(R.transition.shared_element_transition);
getWindow().setSharedElementEnterTransition(transition);
// Postpone the transition. We will start it when the slideshow is ready.
ActivityCompat.postponeEnterTransition(this);
// more code ...
// See next step below.
}
Now we need to clip the shared view in the SecondActivity as well.
// Setup the clips
String transitionName = getIntent().getStringExtra(EXTRA_TRANSITION_NAME);
Rect clipRect = getIntent().getParcelableExtra(EXTRA_CLIP_RECT);
setEnterSharedElementCallback(new SharedElementCallback() {
#Override
public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
for (int i = 0; i < sharedElementNames.size(); i++) {
if (Objects.equals(transitionName, sharedElementNames.get(i))) {
View view = sharedElements.get(i);
view.setClipBounds(clipRect);
}
}
super.onSharedElementStart(sharedElementNames, sharedElements, sharedElementSnapshots);
}
#Override
public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
for (int i = 0; i < sharedElementNames.size(); i++) {
if (Objects.equals(transitionName, sharedElementNames.get(i))) {
View view = sharedElements.get(i);
view.setClipBounds(null);
}
}
super.onSharedElementEnd(sharedElementNames, sharedElements, sharedElementSnapshots);
}
});
I have a framelayout in the layout for a row in a ListView. When an item appears in the listview an AnimationDrawable is triggered.
This works fine on the Nexus 4 and Nexus 5. I had to change from using view.setBackground to view.setBackgroundDrawable to support slightly older devices. Making this change hasn't broken it on either of the Nexus devices. It also worked on an old gingerbread device.
On the HTC One V, running 4.03. the AnimationDrawable never appears. If, however, I hardcode the row xml file to show a standard drawable, rather than the animationdrawable, it appears fine.
Here is the xml file the animationdrawable
<item
android:drawable="#drawable/alert_red"
android:duration="700"/>
<item
android:drawable="#drawable/alert_red_glow"
android:duration="700"/>
</animation-list>
Here is the code which starts the animation
#SuppressWarnings("deprecation")
public void startGlowingRow() {
notificationStatus
.setBackgroundDrawable(getResources().getDrawable(
R.drawable.glowing_emergency_notification));
glowRequest = new GlowRequest();
notificationStatus.post(glowRequest);
}
public void stopGlowingRow() {
try {
// Remove any callbacks to make background glowing
if (glowRequest != null)
notificationStatus.removeCallbacks(glowRequest);
glowDrawable = (AnimationDrawable) notificationStatus
.getBackground();
glowDrawable.stop();
} catch (ClassCastException cce) {
// Not the correct background in view so don't try to stop
}
}
// Runnable which sets glowing effect after view has loaded
private class GlowRequest implements Runnable {
public void run() {
glowDrawable = (AnimationDrawable) notificationStatus
.getBackground();
glowDrawable.start();
}
}
I finally figured this out. It was because the view that contained the animation was using padding to show it's background. When I changed this so that it's first child used a margin instead it worked just fine
I have an imageview with a default image. Now when I try to click it, I want it to animate that displays 4 frames of images. How could I accomplish this, I tried a simpler approach (more of a dumb approach) by changing the imageresource 4 times, as expected the image changes so fast that the animating image effect is not visible. Any ideas?
I tried this approach:
Gem = (ImageView)v;
Gem.setImageResource(com.example.gems.R.drawable.bd1);
Gem.postDelayed(new Runnable() {
public void run() {
Gem.setImageResource(com.example.gems.R.drawable.bd2);
Gem.postDelayed(new Runnable() {
public void run() {
Gem.setImageResource(com.example.gems.R.drawable.bd3);
Gem.postDelayed(new Runnable() {
public void run() {
Gem.setImageResource(com.example.gems.R.drawable.bd4);
Gem.postDelayed(new Runnable() {
public void run() {
}
}, 500);
}
}, 500);
}
}, 500);
}
}, 500);
It worked, but is there a better way to this without coding too much lines? I have 25 kinds of images and each image has 4 frames.
EDIT:
I tried using xml transition files:
Java file:
Resources res = this.getResources();
Gem = (ImageView)v;
TransitionDrawable transition;
transition = (TransitionDrawable)
res.getDrawable(R.drawable.blue_diamond_animation);
Gem.setImageDrawable(transition);
transition.startTransition(3000);
xml file:
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/bd1"></item>
<item android:drawable="#drawable/bd2"></item>
<item android:drawable="#drawable/bd3"></item>
<item android:drawable="#drawable/bd4"></item>
<item android:drawable="#drawable/invi"></item>
</transition>
This seems to work, but I don't want to draw this images in a transition. I want to change the background in a transition. I tried changing this android:drawable to android:drawable but it doesn't work.
It turns out that there is an exact class for this: AnimationDrawable
Basically, just add frames with the other pictures to use in the animation to the AnimationDrawable object and specify how long they should display using addFrame(Drawable frame, int duration)
Then set the ImageView to display whatever image it should start with and set the background to the AnimationDrawable you just made using setBackgroundDrawable(Animation)
Lastly, start the animation in the onClick listener
EDIT: FOR EXAMPLE
AnimationDrawable ad = new AnimationDrawable();
ad.addFrame(getResources().getDrawable(R.drawable.image1), 100);
ad.addFrame(getResources().getDrawable(R.drawable.image2), 500);
ad.addFrame(getResources().getDrawable(R.drawable.image3), 300);
ImageView iv = (ImageView) findViewById(R.id.img);
iv.setBackgroundDrawable(animation);
And then in your onClick listener, just call ad.start
It depends on what you are trying to accomplish. You have not given enough info to go on
The viewproperty animator is really the easiest thing to use have a look. It can also be used with older API's using nineoldandroids jar (google it)
http://developer.android.com/reference/android/view/ViewPropertyAnimator.html
The ObjectAnimator and ValueAnimator are also available similar and slightly more difficult to implement
http://developer.android.com/reference/android/animation/ObjectAnimator.html
http://developer.android.com/reference/android/animation/ValueAnimator.html
Have a look at the APIdemos in the sample packs there are a few examples mainly using the ValueAnimator.
http://developer.android.com/tools/samples/index.html
There are also Sprites to consider but it is basically a bitmap , you can user the timer obect to choreograph