In my recyclerview, I want all items to jiggle/wobble/wiggle when a user holds an item and moves it. The problem I'm facing is that when a user holds the item and moves it in the same viewtype the wiggle is okay but when someone drags it to the top of the recyclerview (which is a header created as a viewtype in the recyclerview) the wiggle increases a lot.
On playing around with the values I realised that this was because even though the rotation angle the same, the farther it moved away from the item's center the rotation used to increase.
I tried doing this with an object animator also but it didn't help as that too had the same issue of the rotation angle.
Here is my wiggle code
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="100"
android:fromDegrees="-5"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:toDegrees="5" />
Here is a video of how it looks -
link
On bindView logic to start animating
((VHItem) holder).rlContainer.setOnLongClickListener(new View.OnLongClickListener()
{
#Override
public boolean onLongClick(View view)
{
if (buPostModelList != null)
{
startAnimationItem = true;
isDragCover = true;
isEditCoverImage = false;
for (int i = 0; i <= buPostModelList.size(); i++)
{
if (recyclerView.getChildAt(i) != null && recyclerView.getChildViewHolder(recyclerView.getChildAt(i)).getItemViewType() != TYPE_HEADER)
{
recyclerView.getChildAt(i).startAnimation(AnimationUtils.loadAnimation(context, R.anim.jiggle));
}
}
touchHelper.startDrag(holder);
}
return true;
}
});
EDIT
A Sample project - link
As you move the view, the center of the rotation remains at the starting location but the view still moves five degrees back and forth, so it is like moving from the center of a merry-go-round to the periphery where the five degree movement covers a greater distance in the same amount of time.
I suggest that you move to an ObjectAnimator which does not have this problem.
ObjectAnimator
This subclass of ValueAnimator provides support for animating properties on target objects. The constructors of this class take parameters to define the target object that will be animated as well as the name of the property that will be animated. Appropriate set/get functions are then determined internally and the animation will call these functions as necessary to animate the property.
jiggle.xml
This is a new ObjectAnimator xml for the jiggle effect. It is very similar to your jiggle.xml.
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="100"
android:propertyName="rotation"
android:repeatCount="-1"
android:repeatMode="reverse"
android:valueFrom="-5"
android:valueTo="5"
android:valueType="floatType" />
VHItem
Updated view holder with animator support.
class VHItem extends RecyclerView.ViewHolder {
private ImageView ivCollectionImage, ivRemoveIcon;
private RelativeLayout rlContainer;
private Animator mAnimator;
public VHItem(View itemView) {
super(itemView);
ivCollectionImage = itemView.findViewById(R.id.ivCollectionImage);
ivRemoveIcon = itemView.findViewById(R.id.ivRemoveIcon);
rlContainer = itemView.findViewById(R.id.rlContainer);
}
// Start animation. Inflate the animator lazily.
public void startAnimator() {
if (mAnimator == null) {
mAnimator = AnimatorInflater.loadAnimator(context, R.animator.jiggle);
}
mAnimator.setTarget(itemView);
mAnimator.start();
}
// Stop the animation. Set the rotation back to zero.
public void stopAnimator() {
if (mAnimator != null) {
itemView.setRotation(0);
mAnimator.cancel();
}
}
}
You will need to update the rest of the adapter to work with the new animation.
recently I wrote a function.it's about a refresh button in each list item. what I want is click the button or List Item, the refresh button starts rotate. stops when the request finished.
I use animation as follows:
<?xml version="1.0" encoding="UTF-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fillAfter="true"
android:fromDegrees="0"
android:interpolator="#android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
android:toDegrees="358" />
and some source code are here:
public void refresh(View v) {
Animation rotation = AnimationUtils.loadAnimation(mContext,
R.anim.rotate);
rotation.setFillAfter(true);
v.startAnimation(rotation);
}
public void completeRefresh(View v) {
v.clearAnimation();
}
when the request finished, I call notifyDataSetChanged() to refresh the LiseView.
the problem is that the button was indeed rotating. but when I click it the second time. it's rotating but a little fuzzy. just like this:
any suggestions? thanks a lot.
What is most likely happening on your second (and subsequent clicks) is that the animation is running on it again.
In your current implementation, try using setTag(...) like this:
public void refresh(View v) {
if(v.getTag() != null && (boolean)v.getTag()) {
//do nothing, since we are setting the tag to be true once pressed
} else {
//this view hasn't been clicked yet, show animation and set tag
v.setTag(true);
Animation rotation = AnimationUtils.loadAnimation(mContext, R.anim.rotate);
rotation.setFillAfter(true);
v.startAnimation(rotation);
}
}
In your Adapter, you should keep track of which list items are being animated (I'm assuming multiple items can be clicked at the same time). Once you know that the 'request' is finished, you can update the adapter with the correct items and then call notifyDataSetChanged()
I have been searching everywhere for a proper solution to my problem and I can't seem to find one yet. I have an ActionBar (ActionBarSherlock) with a menu that is inflated from an XML file and that menu contains one item and that one item is shown as an ActionItem.
menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/menu_refresh"
android:icon="#drawable/ic_menu_refresh"
android:showAsAction="ifRoom"
android:title="Refresh"/>
</menu>
activity:
[...]
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getSupportMenuInflater().inflate(R.menu.mymenu, menu);
return true;
}
[...]
The ActionItem is displayed with an icon and no text however when a user clicks on the ActionItem, I want the icon to begin animating, more specifically, rotating in place. The icon in question is a refresh icon.
I realize that ActionBar has support for using custom views (Adding an Action View) however this custom view is expanded to cover the entire area of the ActionBar and actually blocks everything except the app icon, which in my case is not what I was looking for.
So my next attempt was to try to use AnimationDrawable and define my animation frame-by-frame, set the drawable as the icon for the menu item, and then in onOptionsItemSelected(MenuItem item) get the icon and begin animating using ((AnimationDrawable)item.getIcon()).start(). This however was unsuccessful. Does anyone know of any way to accomplish this effect?
You're on the right track. Here is how the GitHub Gaug.es app will be implementing it.
First they define an animation XML:
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000"
android:interpolator="#android:anim/linear_interpolator" />
Now define a layout for the action view:
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_action_refresh"
style="#style/Widget.Sherlock.ActionButton" />
All we need to do is enable this view whenever the item is clicked:
public void refresh() {
/* Attach a rotating ImageView to the refresh item as an ActionView */
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ImageView iv = (ImageView) inflater.inflate(R.layout.refresh_action_view, null);
Animation rotation = AnimationUtils.loadAnimation(getActivity(), R.anim.clockwise_refresh);
rotation.setRepeatCount(Animation.INFINITE);
iv.startAnimation(rotation);
refreshItem.setActionView(iv);
//TODO trigger loading
}
When the loading is done, simply stop the animation and clear the view:
public void completeRefresh() {
refreshItem.getActionView().clearAnimation();
refreshItem.setActionView(null);
}
And you're done!
Some additional things to do:
Cache the action view layout inflation and animation inflation. They are slow so you only want to do them once.
Add null checks in completeRefresh()
Here's the pull request on the app: https://github.com/github/gauges-android/pull/13/files
I've worked a bit on solution using ActionBarSherlock, I've came up with this:
res/layout/indeterminate_progress_action.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingRight="12dp" >
<ProgressBar
style="#style/Widget.Sherlock.ProgressBar"
android:layout_width="44dp"
android:layout_height="32dp"
android:layout_gravity="left"
android:layout_marginLeft="12dp"
android:indeterminate="true"
android:indeterminateDrawable="#drawable/rotation_refresh"
android:paddingRight="12dp" />
</FrameLayout>
res/layout-v11/indeterminate_progress_action.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center" >
<ProgressBar
style="#style/Widget.Sherlock.ProgressBar"
android:layout_width="32dp"
android:layout_gravity="left"
android:layout_marginRight="12dp"
android:layout_marginLeft="12dp"
android:layout_height="32dp"
android:indeterminateDrawable="#drawable/rotation_refresh"
android:indeterminate="true" />
</FrameLayout>
res/drawable/rotation_refresh.xml
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:pivotX="50%"
android:pivotY="50%"
android:drawable="#drawable/ic_menu_navigation_refresh"
android:repeatCount="infinite" >
</rotate>
Code in activity (I have it in ActivityWithRefresh parent class)
// Helper methods
protected MenuItem refreshItem = null;
protected void setRefreshItem(MenuItem item) {
refreshItem = item;
}
protected void stopRefresh() {
if (refreshItem != null) {
refreshItem.setActionView(null);
}
}
protected void runRefresh() {
if (refreshItem != null) {
refreshItem.setActionView(R.layout.indeterminate_progress_action);
}
}
in activity creating menu items
private static final int MENU_REFRESH = 1;
#Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, MENU_REFRESH, Menu.NONE, "Refresh data")
.setIcon(R.drawable.ic_menu_navigation_refresh)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS);
setRefreshItem(menu.findItem(MENU_REFRESH));
refreshData();
return super.onCreateOptionsMenu(menu);
}
private void refreshData(){
runRefresh();
// work with your data
// for animation to work properly, make AsyncTask to refresh your data
// or delegate work anyhow to another thread
// If you'll have work at UI thread, animation might not work at all
stopRefresh();
}
And the icon, this is drawable-xhdpi/ic_menu_navigation_refresh.png
This could be found in http://developer.android.com/design/downloads/index.html#action-bar-icon-pack
In addition to what Jake Wharton said, you should propably do the following to ensure that the animation stops smoothly and does not jump around as soon as the loading finished.
First, create a new boolean (for the whole class):
private boolean isCurrentlyLoading;
Find the method that starts your loading. Set your boolean to true when the activity starts loading.
isCurrentlyLoading = true;
Find the method that is started when your loading is finished. Instead of clearing the animation, set your boolean to false.
isCurrentlyLoading = false;
Set an AnimationListener on your animation:
animationRotate.setAnimationListener(new AnimationListener() {
Then, each time the animation was executed one time, that means when your icon made one rotation, check the loading state, and if not loading anymore, the animation will stop.
#Override
public void onAnimationRepeat(Animation animation) {
if(!isCurrentlyLoading) {
refreshItem.getActionView().clearAnimation();
refreshItem.setActionView(null);
}
}
This way, the animation can only be stopped if it already rotated till the end and will be repeated shortly AND it is not loading anymore.
This is at least what I did when I wanted to implement Jake's idea.
There is also an option to create the rotation in code. Full snip:
MenuItem item = getToolbar().getMenu().findItem(Menu.FIRST);
if (item == null) return;
// define the animation for rotation
Animation animation = new RotateAnimation(0.0f, 360.0f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(1000);
//animRotate = AnimationUtils.loadAnimation(this, R.anim.rotation);
animation.setRepeatCount(Animation.INFINITE);
ImageView imageView = new ImageView(this);
imageView.setImageDrawable(UIHelper.getIcon(this, MMEXIconFont.Icon.mmx_refresh));
imageView.startAnimation(animation);
item.setActionView(imageView);
With support library we can animate icon without custom actionView.
private AnimationDrawableWrapper drawableWrapper;
#Override
public boolean onCreateOptionsMenu(Menu menu) {
//inflate menu...
MenuItem menuItem = menu.findItem(R.id.your_icon);
Drawable icon = menuItem.getIcon();
drawableWrapper = new AnimationDrawableWrapper(getResources(), icon);
menuItem.setIcon(drawableWrapper);
return true;
}
public void startRotateIconAnimation() {
ValueAnimator animator = ObjectAnimator.ofInt(0, 360);
animator.addUpdateListener(animation -> {
int rotation = (int) animation.getAnimatedValue();
drawableWrapper.setRotation(rotation);
});
animator.start();
}
We can't animate drawable directly, so use DrawableWrapper(from android.support.v7 for API<21):
public class AnimationDrawableWrapper extends DrawableWrapper {
private float rotation;
private Rect bounds;
public AnimationDrawableWrapper(Resources resources, Drawable drawable) {
super(vectorToBitmapDrawableIfNeeded(resources, drawable));
bounds = new Rect();
}
#Override
public void draw(Canvas canvas) {
copyBounds(bounds);
canvas.save();
canvas.rotate(rotation, bounds.centerX(), bounds.centerY());
super.draw(canvas);
canvas.restore();
}
public void setRotation(float degrees) {
this.rotation = degrees % 360;
invalidateSelf();
}
/**
* Workaround for issues related to vector drawables rotation and scaling:
* https://code.google.com/p/android/issues/detail?id=192413
* https://code.google.com/p/android/issues/detail?id=208453
*/
private static Drawable vectorToBitmapDrawableIfNeeded(Resources resources, Drawable drawable) {
if (drawable instanceof VectorDrawable) {
Bitmap b = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
drawable.draw(c);
drawable = new BitmapDrawable(resources, b);
}
return drawable;
}
}
I took idea for DrawableWrapper from here: https://stackoverflow.com/a/39108111/5541688
its my very simple solution (for example, need some refactor)
works with standart MenuItem,
you can use it with any number of states, icons, animations, logic etc.
in Activity class:
private enum RefreshMode {update, actual, outdated}
standart listener:
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh: {
refreshData(null);
break;
}
}
}
into refreshData() do something like this:
setRefreshIcon(RefreshMode.update);
// update your data
setRefreshIcon(RefreshMode.actual);
method for define color or animation for icon:
void setRefreshIcon(RefreshMode refreshMode) {
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Animation rotation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.rotation);
FrameLayout iconView;
switch (refreshMode) {
case update: {
iconView = (FrameLayout) inflater.inflate(R.layout.refresh_action_view,null);
iconView.startAnimation(rotation);
toolbar.getMenu().findItem(R.id.menu_refresh).setActionView(iconView);
break;
}
case actual: {
toolbar.getMenu().findItem(R.id.menu_refresh).getActionView().clearAnimation();
iconView = (FrameLayout) inflater.inflate(R.layout.refresh_action_view_actual,null);
toolbar.getMenu().findItem(R.id.menu_refresh).setActionView(null);
toolbar.getMenu().findItem(R.id.menu_refresh).setIcon(R.drawable.ic_refresh_24dp_actual);
break;
}
case outdated:{
toolbar.getMenu().findItem(R.id.menu_refresh).setIcon(R.drawable.ic_refresh_24dp);
break;
}
default: {
}
}
}
there is 2 layouts with icon (R.layout.refresh_action_view (+ "_actual") ):
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="48dp"
android:layout_height="48dp"
android:gravity="center">
<ImageView
android:src="#drawable/ic_refresh_24dp_actual" // or ="#drawable/ic_refresh_24dp"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="12dp"/>
</FrameLayout>
standart rotate animation in this case (R.anim.rotation) :
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000"
android:repeatCount="infinite"
/>
the best way is here:
public class HomeActivity extends AppCompatActivity {
public static ActionMenuItemView btsync;
public static RotateAnimation rotateAnimation;
#Override
protected void onCreate(Bundle savedInstanceState) {
rotateAnimation = new RotateAnimation(360, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration((long) 2*500);
rotateAnimation.setRepeatCount(Animation.INFINITE);
and then:
private void sync() {
btsync = this.findViewById(R.id.action_sync); //remember that u cant access this view at onCreate() or onStart() or onResume() or onPostResume() or onPostCreate() or onCreateOptionsMenu() or onPrepareOptionsMenu()
if (isSyncServiceRunning(HomeActivity.this)) {
showConfirmStopDialog();
} else {
if (btsync != null) {
btsync.startAnimation(rotateAnimation);
}
Context context = getApplicationContext();
context.startService(new Intent(context, SyncService.class));
}
}
Remember that u cant access "btsync = this.findViewById(R.id.action_sync);" at onCreate() or onStart() or onResume() or onPostResume() or onPostCreate() or onCreateOptionsMenu() or onPrepareOptionsMenu()
if u want get it just after activity start put it in a postdelayed:
public static void refreshSync(Activity context) {
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
public void run() {
btsync = context.findViewById(R.id.action_sync);
if (btsync != null && isSyncServiceRunning(context)) {
btsync.startAnimation(rotateAnimation);
} else if (btsync != null) {
btsync.clearAnimation();
}
}
}, 1000);
}
I have a ListView on screen and a menu at the bottom. Upon a click of menu key, it animates-slides off the screen and the ListView expands.
menuBtmVisable = false;
Animation menu_off = AnimationUtils.loadAnimation(this, R.anim.menu_off);
menubtm.startAnimation(menu_off);
Display display = getWindowManager().getDefaultDisplay();
LayoutParams listlp = new LayoutParams(display.getWidth(), display.getHeight()-87 , 0, 50);
ListViewMain.setLayoutParams(listlp);
menu_off.xml
<set
android:fillEnabled="true"
android:fillAfter="true"
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="0"
android:toYDelta="120"
android:duration="500"
/>
</set>
Yet when, with the menu down, I click on the ListView item "13" ( see picture ), it results in a menu click, as if it is still in place...
What do you think would be the best way to take care of it?
I've looking for an answer for a problem like that, and finally, after a week, I've managed to solve my problem. Since it looks similar to yours, maybe it will help you.
Set up and AnimationListener() in your animation and, onAnimationEnd, change the layout as you want. In my case, I wanted to slide a layout up, so 2 buttons would appear from below. However, the layout just slided visually; the buttons would still be off screen, interactionally speaking. So I have something like this:
final View screen = findViewById(R.id.welcome_screen);
final Animation a = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_home_up);
a.setFillAfter(true);
a.setAnimationListener(new AnimationListener() {
public void onAnimationStart(Animation animation) {
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
screen.clearAnimation();
screen.setPadding(0, -222, 0, 0);
}
});
screen.startAnimation(a);
In my android project I have an image that i want to rotate it and create animation for long time.
I can run program and it works fine. but I don't know how can I stop it. if you know please tell me.
Thanks
This is my code, I have a menu with two items, When I click on Start item, startAnimation function runs and when I click on Stop item, stopAnimation runs.:
public class Hypnagogic extends Activity
{
ImageView imView;
Animation an;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imView = (ImageView)findViewById(R.id.imView);
}
private void startAnimation()
{
an = AnimationUtils.loadAnimation(this, R.anim.spin);
imView.startAnimation(an);
}
private void stopAnimation()
{
;//there is no any stop function!
}
}
also, in res/anim I put spin.xml that is my animation
<?xml version="1.0" encoding="utf-8"?>
<set>
<rotate
android:fromDegrees="0"
android:toDegrees="-18000"
android:pivotX="50%"
android:pivotY="50%"
android:duration="50000" />
</set>
EDIT: I see. Okay, try the clearAnimation() method associated with the View class. You can call it directly on your ImageView, I'm almost certain this should work for your case:
imView.clearAnimation();
What have you tried? AnimationDrawable has a stop() method. You need to post how you're creating/starting the animation.