How to achieve smooth fragment transaction animations on Android - android

I've mostly worked with iOS and have become accustomed to very smooth and fluent screen change animations. I am now working on an Android app and can't for the life of me get a fragment transaction to smoothly add/replace a fragment.
My set up is as follows: MainActivity has a FrameLayout for its xml which I immediately load FragmentA into on the MainActivity's OnCreate. My app then proceeds to replace the MainActivity's FrameLayout with FragmentB, FragmentC, ect. So my entire app has 1 activity.
On button clicks I call the following to add/replace the current fragment:
getFragmentManager()
.beginTransaction()
.setCustomAnimations(R.animator.slide_in, android.R.animator.fade_out, android.R.animator.fade_in, R.animator.slide_out)
.addToBackStack(null)
.add(R.id.fragment_1, fragment)
.commit();
slide_in.xml looks like (slide_out.xml is obviously the opposite):
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:interpolator="#android:interpolator/linear"
android:propertyName="xFraction"
android:valueType="floatType"
android:valueFrom="1.0"
android:valueTo="0"
android:duration="#android:integer/config_shortAnimTime"
/>
</set>
Sliding in and out I'm animating xFraction which I've subclassed LinearLayout to do like so:
public class SlidingLinearLayout extends LinearLayout {
private float yFraction = 0;
private float xFraction = 0;
public SlidingLinearLayout(Context context) {
super(context);
}
public SlidingLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlidingLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private ViewTreeObserver.OnPreDrawListener preDrawListener = null;
public void setYFraction(float fraction) {
this.yFraction = fraction;
if (getHeight() == 0) {
if (preDrawListener == null) {
preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
setYFraction(yFraction);
return true;
}
};
getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
return;
}
float translationY = getHeight() * fraction;
setTranslationY(translationY);
}
public float getYFraction() {
return this.yFraction;
}
public void setXFraction(float fraction) {
this.xFraction = fraction;
if (getWidth() == 0) {
if (preDrawListener == null) {
preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
setXFraction(xFraction);
return true;
}
};
getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
return;
}
float translationX = getWidth() * fraction;
setTranslationX(translationX);
}
public float getxFraction() {
return xFraction;
}
}
So my issue is most of the time when I initially click a button to add/replace a fragment, I don't get an animation it just sort of pops into place. However more times than not when I press the back button and the fragment pops from the backstack the animation executes as expected. I feel like it is an issue with initialization while trying to animate. When the fragment is in memory and animated off screen it does so much better than when a new fragment is created then animated on to the screen. I'm curious if others have experienced this and if so how they resolved it.
This is an annoyance that has plagued my Android development experience that I'd really like to put to rest! Any help would be greatly appreciated. Thanks!

In my experience, if the fragment is doing too much work on load, then the system will basically skip the enter animation and just display the fragment. In my case the solution was to create an animation listener in onCreateAnimation to wait until the enter animation had finished before doing the process intensive work that was causing the animation to be skipped.
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
if (nextAnim == 0) {
return super.onCreateAnimation(transit, enter, nextAnim);
}
Animation anim = android.view.animation.AnimationUtils.loadAnimation(getContext(), nextAnim);
anim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
// Do any process intensive work that can wait until after fragment has loaded
}
#Override
public void onAnimationRepeat(Animation animation) {}
});
return anim;
}

Related

RecyclerView - SlideIn animation on activity start

How can I add a slide in animation on my recycler view items one after the other. Like as the activity starts, the list items of the recycler view slides in one by one. I am using LinearLayoutManager
Not all at the same time should slide in. And not even while scrolling. Just at the time of activity creation.
I searched but didn't find anything.
I want to achieve something like this : https://youtu.be/Q8TXgCzxEnw?t=30s
I put together a sample app a couple of months ago that has a sequential slide in-slide out animation during reshuffles. A demo video is available here. It should give you some ideas.
A link to the most relevant class file is here, and I'll copy the code below.
public class AllNotesFragmentRecyclerView extends RecyclerView {
private static final int BASE_ANIMATION_TIME = 50;
private static final int MAX_ANIMATION_TIME_INCREMENT = 100;
private int screenWidth;
private int startX, finalX;
private int[] interpolatedAnimationTimes;
public AllNotesFragmentRecyclerView(Context context) {
super(context);
init(context);
}
public AllNotesFragmentRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public AllNotesFragmentRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
calculateScreenWidth(context);
startX = 0;
finalX = -(screenWidth);
}
private void calculateScreenWidth(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
screenWidth = metrics.widthPixels;
}
private int calculateInterpolatedAnimationTime(int currentIndex, int maxIndex) {
float percentage = ((float)currentIndex/(float)maxIndex);
float increment = (float) MAX_ANIMATION_TIME_INCREMENT * percentage;
return (int) (BASE_ANIMATION_TIME + increment);
}
public void updateListOrder() {
createAnimatorSet();
}
private void createAnimatorSet() {
AnimatorSet set = new AnimatorSet();
ArrayList<Animator> animArrayList = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
ObjectAnimator anim = ObjectAnimator
.ofFloat(getChildAt(i), "translationX", finalX);
int duration = calculateInterpolatedAnimationTime(i, getChildCount());
anim.setDuration(duration);
anim.addListener(new RowAnimationListener(i, duration, startX));
animArrayList.add(anim);
}
set.setInterpolator(new AccelerateInterpolator());
set.playSequentially(animArrayList);
set.start();
}
private void animateOn(int childPosition, int duration, int targetValue) {
ObjectAnimator animator = ObjectAnimator
.ofFloat(getChildAt(childPosition), "translationX", targetValue);
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration(duration);
animator.start();
}
//...
private class RowAnimationListener implements Animator.AnimatorListener {
private int position, duration, targetX;
public RowAnimationListener(int position, int duration, int targetX) {
this.position = position;
this.duration = duration;
this.targetX = targetX;
}
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
int currentItem = getLinearLayoutManager().findFirstVisibleItemPosition() + position;
getAdapter().notifyItemChanged(currentItem);
notifyRowsPeripheralToVisibleItemsDataChanged(position);
animateOn(position, duration, targetX);
}
#Override
public void onAnimationCancel(Animator animation) { }
#Override
public void onAnimationRepeat(Animator animation) { }
}
}
Finally I found a solution. In below snippet I will explain how to implement. It is simple and can be done on any existing working RecyclerView. I have explained everything in comments.
Here is the onCreate/onCreateView method (I have used this inside Fragment, You can change accordingly if needed):
RecyclerView recList = (RecyclerView) rootView.findViewById(R.id.event_list);
recList.setHasFixedSize(true);
LinearLayoutmanager llm = new LinearLayoutManager(getActivity().getApplicationContext());
llm.setOrientation(LinearLayoutManager.VERTICAL);
recList.setLayoutManager(llm);
// This is important. Setting recyclerView's alpha to zero.
// Basically this is just to hide recyclerview at start before binding data
// As setVisibility is not working on recycler view object.
recList.setAlpha(0);
// Create the EventAdapter with the result we got
// EventAdapter is my custom adapter class.
// you should set your adapter class
EventAdapter ea = new EventAdapter(eventResultList);
// Binding the Adapter to RecyclerView list to show the data
recList.setAdapter(ea);
// ********************* Animate at start ********************************
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// This will give me the initial first and last visible element's position.
// This is required as only this elements needs to be animated
// Start will be always zero in this case as we are calling in onCreate
int start = llm.findFirstVisibleItemPosition();
int end = llm.findLastVisibleItemPosition();
Log.i("Start: ", start + "");
Log.i("End: ", end + "");
// Multiplication factor
int DELAY = 50;
// Loop through all visible element
for (int i = start; i <= end; i++) {
Log.i("Animatining: ", i + "");
// Get View
View v = recList.findViewHolderForAdapterPosition(i).itemView;
// Hide that view initially
v.setAlpha(0);
// Setting animations: slide and alpha 0 to 1
PropertyValuesHolder slide = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 150, 0);
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat(View.ALPHA, 0, 1);
ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(v, slide, alpha);
a.setDuration(300);
// It will set delay. As loop progress it will increment
// And it will look like items are appearing one by one.
// Not all at a time
a.setStartDelay(i * DELAY);
a.setInterpolator(new DecelerateInterpolator());
a.start();
}
// Set Recycler View visible as all visible are now hidden
// Animation will start, so set it visible
recList.setAlpha(1);
}
}, 50);
This is quite a small code without comments.
Some things needs an explanation:
Why hiding RecyclerView initially?
If RecyclerView is not hidden initially you will notice a blink initially before the animation starts. The reason for it is when you set a data adapter it will position it on its default positions and after the loop it starts animating. So in between while loop is running you will notice sudden blink in the RecyclerView that at first all are at its initial position and than suddenly animating.
So hiding it at first and than after loop completes and all visible positions animations are set with delays and started, we can show the RecyclerView. It makes sliding looks smooth.
The reason for hiding it with setAlpha(0) is as setVisibility() function is not working on the RecyclerView object.
How only visible elements will animate?
There are functions in the LayoutManager class to get the visible elements position. In LinearLayoutManager used findFirstVisibleItemPosition() to get the position of the first visible view from the recycler view data which is visible on screen. And the last visible view's position can be retried with findLastVisibleItemPosition(). So we can loop from the first view to last view and animate the initial views which are going to be on screen at start.
How delay Works?
As loop will progress from 0(start) to end it will set delay from 0,50,100,150,.. if DELAY variable is set to 50. So this will make first element start animating, second after 50ms delay, third after 100ms delay and so on. So it will look like they are coming in one by one. Not all together.
Create animation in anim/slide_in.xml file like below
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="#android:anim/decelerate_interpolator">
<translate
android:fromXDelta="100%" android:toXDelta="0%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="2000"/>
</set>
And then apply this animation on each view of RecyclerView in onBindViewHolder method.
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ViewHolder vh = (ViewHolder) holder;
vh1.tv_header.setText(mList.get(position));
Animation animation = AnimationUtils.loadAnimation(mContext,R.anim.rec_anim);
animation.setStartOffset(30 * position);//Provide delay here
holder.itemView.startAnimation(animation);
}

Android - How would I go about implementing an activity that I can drag horizontally? [duplicate]

Does anybody has an idea how Pinterest or Tumblr has implemented there "swipe back" method.
i.e. on Pinterest you can click on a post on the news feed. Than the DetailActivity is started and displays the details for the selected post. Than you can press the back button to return to the news feed activity, or you can swipe (the details activity) to the left to come back to the news feed activity.
Video: http://youtu.be/eVcSCWetnTA
Normally I would use overridePendingTransition(), but overridePendingTransition() takes animations (Resource ids like R.anim.foo). Pinterest and Tumblr start the animation only if the user do a swipe gesture. They also support some kind of "frame by frame animation" according the fingers move. So they track the distance of the finger move and animate the transition to the corresponding percentage value.
I know how to use a "real java" Animation / AnimatorSet Object with FragmentTransaction to animate a fragment replacement. With fragments I have to override onCreateAnimator(), but I have no clue how to implement something like that with Activities. Is there a onCreateAnimator() (or something similar) for Activities? Also not sure how to swipe behaviour, since its not starting the animation right now, but more a step by step property changement of the Window / Activity/ Fragment or whatever ...
Any suggestions?
EDIT:
I have found a video of the pinterest app at youtube: http://youtu.be/eVcSCWetnTA
Thats what I want to implement.
I guess Pinterest is working with Fragments and onCreateAnimator() to achieve the "swipe back".
Since my App has already Fragment and ChildFragments in a activity it would be so much easier for me if I could implement that for Activities.
Once more: I know how to detect swipe gestures and thats not what I'm asking for. Watch the youtube video: http://youtu.be/eVcSCWetnTA
UPDATE:
I have created a little library, which has not exactly the same behavior like Pinterest or Tumblrs implementation, however for my apps this seems to me a good solution:
https://github.com/sockeqwe/SwipeBack?source=c
It seems that the effect you're looking for is one of the samples for ViewPager in the android developer's website.
Check out http://developer.android.com/training/animation/screen-slide.html#depth-page , in the Depth page transformer section. It has a video and source code.
Using a ViewPager.PageTransformer you can decide how the pages behave when switching from one to the next.
The only difference between the sample and the video you linked to is that left-right seems to be inverted, but should be a good starting point for what I saw on the YouTube video linked in the question. The actions on the two views would have to be swaped. As shown in this piece of code (the 1st parameter to mPager.setPageTransformer should be reverseDrawingOrder = false). Note the middle 2 if sections are swaped and the position variable is handled slightly different to switch sides. The bouncy effect is left as an exercise. Please share when you get that!
package com.example.android.animationsdemo;
import android.support.v4.view.ViewPager;
import android.view.View;
public class SinkPageTransformer implements ViewPager.PageTransformer {
private static float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Fade the page out.
view.setAlpha(1 + position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else if (position <= 1) { // (0,1]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
And just in case the page with the sample goes poof, here's that section's original code:
public class DepthPageTransformer implements ViewPager.PageTransformer {
private static float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else if (position <= 1) { // (0,1]
// Fade the page out.
view.setAlpha(1 - position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
Update:
fixed memory usage problem for this project and changed the slide back style to iOS like.
I wrote a demo exactly like Pinterest and tumblr like,you just extend the BaseActivity and you get a swipe back effect,works smoothly!
check this:https://github.com/chenjishi/SlideActivity
and the screenshot:
I found a GitHub project that is based on SwipeBack like Pinterest.
It is really a great open source project that should solve your problem. It does as you needed like go to previous screen by pressing back or simple swipe. As this project having option
1. Swipe left to Right
2. Swipe Right to Left
3. Swipe Bottom to top
https://github.com/Issacw0ng/SwipeBackLayout
and also you install this demo application from Google Play.
https://play.google.com/store/apps/details?id=me.imid.swipebacklayout.demo
Attached Screenshots:-
Hope this will help you.
I was able to do this in 15 minutes, it is not bad for a start. If you spend some time, you might be able to optimize it.
package mobi.sherif.activitydrag;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout.LayoutParams;
public class MainActivity extends Activity {
private static final double PERCENT_OF_SCROLL_OF_ACTIVITY_TO_FINISH = 0.3;
View mView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mView = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
setContentView(mView);
}
private boolean isDragging = false;
int startX;
int currentX;
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.v("sherif", isDragging?"YES":"NO" + ": " + event.getX());
if(!isDragging) {
if(event.getAction() == MotionEvent.ACTION_DOWN && event.getX()<24) {
isDragging = true;
startX = (int) event.getX();
currentX = 0;
return true;
}
return super.onTouchEvent(event);
}
switch(event.getAction()) {
case MotionEvent.ACTION_MOVE:
currentX = (int) event.getX() - startX;
LayoutParams params = (LayoutParams) mView.getLayoutParams();
params.leftMargin = currentX;
params.rightMargin = -1 * currentX;
mView.requestLayout();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
isDragging = false;
double currentPercent1 = (double) currentX / mView.getWidth();
float currentPercent = (float) currentPercent1;
if(currentX > PERCENT_OF_SCROLL_OF_ACTIVITY_TO_FINISH * mView.getWidth()) {
AnimationSet animation = new AnimationSet(false);
Animation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 1.0f - currentPercent, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
anim.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
anim.setInterpolator(new LinearInterpolator());
anim.setStartTime(AnimationUtils.currentAnimationTimeMillis());
animation.addAnimation(anim);
anim = new AlphaAnimation(1.0f, 0.5f);
anim.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime));
anim.setInterpolator(new LinearInterpolator());
anim.setStartTime(AnimationUtils.currentAnimationTimeMillis());
animation.addAnimation(anim);
animation.setFillAfter(true);
animation.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
finish();
}
});
mView.startAnimation(animation);
}
else {
AnimationSet animation = new AnimationSet(false);
Animation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f -1 * currentPercent, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
anim.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime));
anim.setInterpolator(new LinearInterpolator());
anim.setStartTime(AnimationUtils.currentAnimationTimeMillis());
animation.addAnimation(anim);
animation.setFillAfter(true);
animation.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
LayoutParams params = (LayoutParams) mView.getLayoutParams();
params.leftMargin = 0;
params.rightMargin = 0;
mView.requestLayout();
mView.clearAnimation();
}
});
mView.startAnimation(animation);
}
break;
}
return true;
}
}
I just checked with hierarchy viewer. It seems like they are using ViewPager with a screenshot of the previous activity.
I would suggest doing the following:
Firstly detect the gesture that the user is doing in the device. You can refer to this link
I am not going to copy the relevant code from the above link, as I believe it is the accepted answer
Secondly you can in this method
public void onSwipeLeft() {
Toast.makeText(MyActivity.this, "left", Toast.LENGTH_SHORT).show();
}
Do the following as suggested by this question
They there talk about finishing an activity with an animation
Look into doing it through a theme. You can define enter exit animations for activities or the entire application
Hope this helps you
So I guess I found the solution by my self:
First of all:
Pinterest indeed uses a ViewPager with a custom Page Transformer like #frozenkoi has mentioned in his answer. You can see the oversroll edge effect of the view pager in the pinterest app.
#Amit Gupta has pointed to library that let the activity slide. Its pretty the same concept like various Navigation drawers does and sets also the theme to translucent. They slide the layout. But thats not exactly what I was looking for, because it slides the top activity to the right and than simply calls finish(). But the underneath activity will not be animated in.
The solution is (and I guess this is was Tumblr does) to write your very own Animation with Animation Objects and animate it step by step.
This can be done with ActivityOptions. In my opinion this will be the solution.
I wrote a project. It allows you to develop an app navigated by Fragments easily, performs just like Pinterest.
https://github.com/fengdai/FragmentMaster
Maybe it's not the answer what you want. But I hope it's useful to someone else.
I was dealing with this one in project I am currently working on and came up with following code. Maybe it's not relevant for you now, but it could help someone new in this post. :)
Basically it's ViewPager implementation as you mention in your answer, but I think it's the simplest and quickest solution to your question. The cons are that it's only for Fragments (could be easily changed for Objects) and if you want to add ActionBar into swipe, you probably end up creating a custom one.
public class DoubleViewPager extends FrameLayout implements ViewPager.OnPageChangeListener {
/**
* Represents number of objects in DelegateViewPager. In others words it stands for one main content
* window and one content detail window
*/
private static final int SCREEN_COUNT = 2;
private static final int CONTENT_SCREEN = 0;
private static final int DETAIL_SCREEN = 1;
private DelegateViewPager delegateViewPager;
private SparseArray<Fragment> activeScreens = new SparseArray<Fragment>(SCREEN_COUNT) ;
private DelegateAdapter adapter;
public DoubleViewPager(Context context) {
this(context, null);
}
public DoubleViewPager(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DoubleViewPager(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
delegateViewPager = new DelegateViewPager(context);
delegateViewPager.setId(R.id.main_page_id);
delegateViewPager.setOverScrollMode(ViewPager.OVER_SCROLL_NEVER);
final FrameLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);
addView(delegateViewPager, params);
}
/**
* Create a new PagerAdapter and set content fragment as a first object in ViewPager;
* #param fragment Fragment you want to use as a main content
* #param fm FragmentManager required for ViewPager transactions
*/
public void initialize(final Fragment fragment, final FragmentManager fm) {
adapter = new DelegateAdapter(fm);
delegateViewPager.setAdapter(adapter);
activeScreens.put(CONTENT_SCREEN, fragment);
adapter.notifyDataSetChanged();
}
/**
* Adds fragment to stack and set it as current selected item. Basically it the same thing as calling
* startActivity() with some transitions effects
* #param fragment Fragment you want go into
*/
public void openDetailScreen(Fragment fragment) {
activeScreens.put(DETAIL_SCREEN, fragment);
adapter.notifyDataSetChanged();
delegateViewPager.setCurrentItem(1, true);
}
public void hideDetailScreen() {
delegateViewPager.setCurrentItem(CONTENT_SCREEN);
if (activeScreens.get(DETAIL_SCREEN) != null) {
activeScreens.remove(DETAIL_SCREEN);
adapter.notifyDataSetChanged();
}
}
#Override
public void onPageScrolled(int i, float v, int i2) {
// unused
}
#Override
public void onPageSelected(int i) {
if (i == CONTENT_SCREEN) hideDetailScreen();
}
#Override
public void onPageScrollStateChanged(int i) {
// unused
}
private class DelegateViewPager extends ViewPager {
public DelegateViewPager(Context context) {
super(context);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return getCurrentItem() != CONTENT_SCREEN && super.onInterceptTouchEvent(event);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return getCurrentItem() != CONTENT_SCREEN && super.onTouchEvent(event);
}
}
private final class DelegateAdapter extends FragmentPagerAdapter {
public DelegateAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
return activeScreens.get(i);
}
#Override
public int getCount() {
return activeScreens.size();
}
}
}
Here is activity which implements it with another ViewPager as a SlidingMenu. (as extra)
public class DoubleViewPagerActivity extends FragmentActivity {
DoubleViewPager doubleViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_double_view_pager);
doubleViewPager = (DoubleViewPager) findViewById(R.id.doublePager);
doubleViewPager.initialize(new MainContentFragment(), getSupportFragmentManager());
}
public static final class MainContentFragment extends Fragment {
public MainContentFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_doublepager_content_window, parent, false);
final ViewPager pager = (ViewPager) view.findViewById(R.id.contentPager);
pager.setAdapter(new SimpleMenuAdapter(getChildFragmentManager()));
pager.setOffscreenPageLimit(2);
pager.setCurrentItem(1);
return view;
}
}
public static final class SimpleMenuAdapter extends FragmentPagerAdapter {
public SimpleMenuAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
return DoubleViewPagerActivity.PagerFragment.instance(i);
}
#Override
public int getCount() {
return 3;
}
#Override
public float getPageWidth(int position) {
switch (position) {
case 0:
case 2:
return 0.7f;
}
return super.getPageWidth(position);
}
}
public static final class PagerFragment extends Fragment {
public static PagerFragment instance(int position) {
final PagerFragment fr = new PagerFragment();
Bundle args = new Bundle();
args.putInt("position", position);
fr.setArguments(args);
return fr;
}
public PagerFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
final FrameLayout fl = new FrameLayout(getActivity());
fl.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
int position = getArguments().getInt("position");
switch (position) {
case 0:
fl.setBackgroundColor(Color.RED);
break;
case 1:
fl.setBackgroundColor(Color.GREEN);
initListView(fl);
break;
case 2:
fl.setBackgroundColor(Color.BLUE);
break;
}
return fl;
}
private void initListView(FrameLayout fl) {
int max = 50;
final ArrayList<String> items = new ArrayList<String>(max);
for (int i = 1; i <= max; i++) {
items.add("Items " + i);
}
ListView listView = new ListView(getActivity());
fl.addView(listView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
listView.setAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, items));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
((DoubleViewPagerActivity) getActivity()).doubleViewPager.openDetailScreen(new DetailFragment());
}
});
}
}
public final static class DetailFragment extends Fragment {
public DetailFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
FrameLayout l = new FrameLayout(getActivity());
l.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
l.setBackgroundColor(getResources().getColor(android.R.color.holo_purple));
return l;
}
}
}
This library allows you to open fragments as in iOS and close them using swipes
https://github.com/shikleev/fragula
Navigation library
Here is a library fully integrated with NavComponent.
I'm still working on it, but it's already stable so you can use it in production.
https://github.com/massivemadness/Fragula
Note: it works only with fragments

Swipe Back like Pinterest or Tumblr

Does anybody has an idea how Pinterest or Tumblr has implemented there "swipe back" method.
i.e. on Pinterest you can click on a post on the news feed. Than the DetailActivity is started and displays the details for the selected post. Than you can press the back button to return to the news feed activity, or you can swipe (the details activity) to the left to come back to the news feed activity.
Video: http://youtu.be/eVcSCWetnTA
Normally I would use overridePendingTransition(), but overridePendingTransition() takes animations (Resource ids like R.anim.foo). Pinterest and Tumblr start the animation only if the user do a swipe gesture. They also support some kind of "frame by frame animation" according the fingers move. So they track the distance of the finger move and animate the transition to the corresponding percentage value.
I know how to use a "real java" Animation / AnimatorSet Object with FragmentTransaction to animate a fragment replacement. With fragments I have to override onCreateAnimator(), but I have no clue how to implement something like that with Activities. Is there a onCreateAnimator() (or something similar) for Activities? Also not sure how to swipe behaviour, since its not starting the animation right now, but more a step by step property changement of the Window / Activity/ Fragment or whatever ...
Any suggestions?
EDIT:
I have found a video of the pinterest app at youtube: http://youtu.be/eVcSCWetnTA
Thats what I want to implement.
I guess Pinterest is working with Fragments and onCreateAnimator() to achieve the "swipe back".
Since my App has already Fragment and ChildFragments in a activity it would be so much easier for me if I could implement that for Activities.
Once more: I know how to detect swipe gestures and thats not what I'm asking for. Watch the youtube video: http://youtu.be/eVcSCWetnTA
UPDATE:
I have created a little library, which has not exactly the same behavior like Pinterest or Tumblrs implementation, however for my apps this seems to me a good solution:
https://github.com/sockeqwe/SwipeBack?source=c
It seems that the effect you're looking for is one of the samples for ViewPager in the android developer's website.
Check out http://developer.android.com/training/animation/screen-slide.html#depth-page , in the Depth page transformer section. It has a video and source code.
Using a ViewPager.PageTransformer you can decide how the pages behave when switching from one to the next.
The only difference between the sample and the video you linked to is that left-right seems to be inverted, but should be a good starting point for what I saw on the YouTube video linked in the question. The actions on the two views would have to be swaped. As shown in this piece of code (the 1st parameter to mPager.setPageTransformer should be reverseDrawingOrder = false). Note the middle 2 if sections are swaped and the position variable is handled slightly different to switch sides. The bouncy effect is left as an exercise. Please share when you get that!
package com.example.android.animationsdemo;
import android.support.v4.view.ViewPager;
import android.view.View;
public class SinkPageTransformer implements ViewPager.PageTransformer {
private static float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Fade the page out.
view.setAlpha(1 + position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else if (position <= 1) { // (0,1]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
And just in case the page with the sample goes poof, here's that section's original code:
public class DepthPageTransformer implements ViewPager.PageTransformer {
private static float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else if (position <= 1) { // (0,1]
// Fade the page out.
view.setAlpha(1 - position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
Update:
fixed memory usage problem for this project and changed the slide back style to iOS like.
I wrote a demo exactly like Pinterest and tumblr like,you just extend the BaseActivity and you get a swipe back effect,works smoothly!
check this:https://github.com/chenjishi/SlideActivity
and the screenshot:
I found a GitHub project that is based on SwipeBack like Pinterest.
It is really a great open source project that should solve your problem. It does as you needed like go to previous screen by pressing back or simple swipe. As this project having option
1. Swipe left to Right
2. Swipe Right to Left
3. Swipe Bottom to top
https://github.com/Issacw0ng/SwipeBackLayout
and also you install this demo application from Google Play.
https://play.google.com/store/apps/details?id=me.imid.swipebacklayout.demo
Attached Screenshots:-
Hope this will help you.
I was able to do this in 15 minutes, it is not bad for a start. If you spend some time, you might be able to optimize it.
package mobi.sherif.activitydrag;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout.LayoutParams;
public class MainActivity extends Activity {
private static final double PERCENT_OF_SCROLL_OF_ACTIVITY_TO_FINISH = 0.3;
View mView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mView = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
setContentView(mView);
}
private boolean isDragging = false;
int startX;
int currentX;
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.v("sherif", isDragging?"YES":"NO" + ": " + event.getX());
if(!isDragging) {
if(event.getAction() == MotionEvent.ACTION_DOWN && event.getX()<24) {
isDragging = true;
startX = (int) event.getX();
currentX = 0;
return true;
}
return super.onTouchEvent(event);
}
switch(event.getAction()) {
case MotionEvent.ACTION_MOVE:
currentX = (int) event.getX() - startX;
LayoutParams params = (LayoutParams) mView.getLayoutParams();
params.leftMargin = currentX;
params.rightMargin = -1 * currentX;
mView.requestLayout();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
isDragging = false;
double currentPercent1 = (double) currentX / mView.getWidth();
float currentPercent = (float) currentPercent1;
if(currentX > PERCENT_OF_SCROLL_OF_ACTIVITY_TO_FINISH * mView.getWidth()) {
AnimationSet animation = new AnimationSet(false);
Animation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 1.0f - currentPercent, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
anim.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
anim.setInterpolator(new LinearInterpolator());
anim.setStartTime(AnimationUtils.currentAnimationTimeMillis());
animation.addAnimation(anim);
anim = new AlphaAnimation(1.0f, 0.5f);
anim.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime));
anim.setInterpolator(new LinearInterpolator());
anim.setStartTime(AnimationUtils.currentAnimationTimeMillis());
animation.addAnimation(anim);
animation.setFillAfter(true);
animation.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
finish();
}
});
mView.startAnimation(animation);
}
else {
AnimationSet animation = new AnimationSet(false);
Animation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f -1 * currentPercent, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
anim.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime));
anim.setInterpolator(new LinearInterpolator());
anim.setStartTime(AnimationUtils.currentAnimationTimeMillis());
animation.addAnimation(anim);
animation.setFillAfter(true);
animation.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
LayoutParams params = (LayoutParams) mView.getLayoutParams();
params.leftMargin = 0;
params.rightMargin = 0;
mView.requestLayout();
mView.clearAnimation();
}
});
mView.startAnimation(animation);
}
break;
}
return true;
}
}
I just checked with hierarchy viewer. It seems like they are using ViewPager with a screenshot of the previous activity.
I would suggest doing the following:
Firstly detect the gesture that the user is doing in the device. You can refer to this link
I am not going to copy the relevant code from the above link, as I believe it is the accepted answer
Secondly you can in this method
public void onSwipeLeft() {
Toast.makeText(MyActivity.this, "left", Toast.LENGTH_SHORT).show();
}
Do the following as suggested by this question
They there talk about finishing an activity with an animation
Look into doing it through a theme. You can define enter exit animations for activities or the entire application
Hope this helps you
So I guess I found the solution by my self:
First of all:
Pinterest indeed uses a ViewPager with a custom Page Transformer like #frozenkoi has mentioned in his answer. You can see the oversroll edge effect of the view pager in the pinterest app.
#Amit Gupta has pointed to library that let the activity slide. Its pretty the same concept like various Navigation drawers does and sets also the theme to translucent. They slide the layout. But thats not exactly what I was looking for, because it slides the top activity to the right and than simply calls finish(). But the underneath activity will not be animated in.
The solution is (and I guess this is was Tumblr does) to write your very own Animation with Animation Objects and animate it step by step.
This can be done with ActivityOptions. In my opinion this will be the solution.
I wrote a project. It allows you to develop an app navigated by Fragments easily, performs just like Pinterest.
https://github.com/fengdai/FragmentMaster
Maybe it's not the answer what you want. But I hope it's useful to someone else.
I was dealing with this one in project I am currently working on and came up with following code. Maybe it's not relevant for you now, but it could help someone new in this post. :)
Basically it's ViewPager implementation as you mention in your answer, but I think it's the simplest and quickest solution to your question. The cons are that it's only for Fragments (could be easily changed for Objects) and if you want to add ActionBar into swipe, you probably end up creating a custom one.
public class DoubleViewPager extends FrameLayout implements ViewPager.OnPageChangeListener {
/**
* Represents number of objects in DelegateViewPager. In others words it stands for one main content
* window and one content detail window
*/
private static final int SCREEN_COUNT = 2;
private static final int CONTENT_SCREEN = 0;
private static final int DETAIL_SCREEN = 1;
private DelegateViewPager delegateViewPager;
private SparseArray<Fragment> activeScreens = new SparseArray<Fragment>(SCREEN_COUNT) ;
private DelegateAdapter adapter;
public DoubleViewPager(Context context) {
this(context, null);
}
public DoubleViewPager(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DoubleViewPager(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
delegateViewPager = new DelegateViewPager(context);
delegateViewPager.setId(R.id.main_page_id);
delegateViewPager.setOverScrollMode(ViewPager.OVER_SCROLL_NEVER);
final FrameLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);
addView(delegateViewPager, params);
}
/**
* Create a new PagerAdapter and set content fragment as a first object in ViewPager;
* #param fragment Fragment you want to use as a main content
* #param fm FragmentManager required for ViewPager transactions
*/
public void initialize(final Fragment fragment, final FragmentManager fm) {
adapter = new DelegateAdapter(fm);
delegateViewPager.setAdapter(adapter);
activeScreens.put(CONTENT_SCREEN, fragment);
adapter.notifyDataSetChanged();
}
/**
* Adds fragment to stack and set it as current selected item. Basically it the same thing as calling
* startActivity() with some transitions effects
* #param fragment Fragment you want go into
*/
public void openDetailScreen(Fragment fragment) {
activeScreens.put(DETAIL_SCREEN, fragment);
adapter.notifyDataSetChanged();
delegateViewPager.setCurrentItem(1, true);
}
public void hideDetailScreen() {
delegateViewPager.setCurrentItem(CONTENT_SCREEN);
if (activeScreens.get(DETAIL_SCREEN) != null) {
activeScreens.remove(DETAIL_SCREEN);
adapter.notifyDataSetChanged();
}
}
#Override
public void onPageScrolled(int i, float v, int i2) {
// unused
}
#Override
public void onPageSelected(int i) {
if (i == CONTENT_SCREEN) hideDetailScreen();
}
#Override
public void onPageScrollStateChanged(int i) {
// unused
}
private class DelegateViewPager extends ViewPager {
public DelegateViewPager(Context context) {
super(context);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return getCurrentItem() != CONTENT_SCREEN && super.onInterceptTouchEvent(event);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return getCurrentItem() != CONTENT_SCREEN && super.onTouchEvent(event);
}
}
private final class DelegateAdapter extends FragmentPagerAdapter {
public DelegateAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
return activeScreens.get(i);
}
#Override
public int getCount() {
return activeScreens.size();
}
}
}
Here is activity which implements it with another ViewPager as a SlidingMenu. (as extra)
public class DoubleViewPagerActivity extends FragmentActivity {
DoubleViewPager doubleViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_double_view_pager);
doubleViewPager = (DoubleViewPager) findViewById(R.id.doublePager);
doubleViewPager.initialize(new MainContentFragment(), getSupportFragmentManager());
}
public static final class MainContentFragment extends Fragment {
public MainContentFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_doublepager_content_window, parent, false);
final ViewPager pager = (ViewPager) view.findViewById(R.id.contentPager);
pager.setAdapter(new SimpleMenuAdapter(getChildFragmentManager()));
pager.setOffscreenPageLimit(2);
pager.setCurrentItem(1);
return view;
}
}
public static final class SimpleMenuAdapter extends FragmentPagerAdapter {
public SimpleMenuAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
return DoubleViewPagerActivity.PagerFragment.instance(i);
}
#Override
public int getCount() {
return 3;
}
#Override
public float getPageWidth(int position) {
switch (position) {
case 0:
case 2:
return 0.7f;
}
return super.getPageWidth(position);
}
}
public static final class PagerFragment extends Fragment {
public static PagerFragment instance(int position) {
final PagerFragment fr = new PagerFragment();
Bundle args = new Bundle();
args.putInt("position", position);
fr.setArguments(args);
return fr;
}
public PagerFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
final FrameLayout fl = new FrameLayout(getActivity());
fl.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
int position = getArguments().getInt("position");
switch (position) {
case 0:
fl.setBackgroundColor(Color.RED);
break;
case 1:
fl.setBackgroundColor(Color.GREEN);
initListView(fl);
break;
case 2:
fl.setBackgroundColor(Color.BLUE);
break;
}
return fl;
}
private void initListView(FrameLayout fl) {
int max = 50;
final ArrayList<String> items = new ArrayList<String>(max);
for (int i = 1; i <= max; i++) {
items.add("Items " + i);
}
ListView listView = new ListView(getActivity());
fl.addView(listView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
listView.setAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, items));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
((DoubleViewPagerActivity) getActivity()).doubleViewPager.openDetailScreen(new DetailFragment());
}
});
}
}
public final static class DetailFragment extends Fragment {
public DetailFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
FrameLayout l = new FrameLayout(getActivity());
l.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
l.setBackgroundColor(getResources().getColor(android.R.color.holo_purple));
return l;
}
}
}
This library allows you to open fragments as in iOS and close them using swipes
https://github.com/shikleev/fragula
Navigation library
Here is a library fully integrated with NavComponent.
I'm still working on it, but it's already stable so you can use it in production.
https://github.com/massivemadness/Fragula
Note: it works only with fragments

Animating button presses / clicks in Android

I'm trying to create a button-click animation, e.g. the button scales down a little when you press it, scales back up when you release. If you simply tap, you get the press and release strung together.
I set up an onTouchListener and a couple XML-defined AnimatorSets, one for the press and one for the release. Ran the press on ACTION_DOWN, the release on ACTION_UP or ACTION_CANCEL. This works fine when you press and hold the button, then release a little later. But with a quick tap, the release animation triggers before the press one is done, and often the result is no animation at all.
I'd hoped I could use AnimatorSet's sequential capabilities to stick the release animation onto the end of the possibly-already-running press animation, but no luck. I'm sure I could rig something up with callbacks, but that seems messy.
What's the best approach here? Thanks!
I built a sample Button class that buffers animations and executes them in a queue according to what you need:
public class AnimationButton extends Button
{
private List<Animation> mAnimationBuffer = new ArrayList<Animation>();;
private boolean mIsAnimating;
public AnimationButton(Context context)
{
super(context);
}
public AnimationButton(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public AnimationButton(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
generateAnimation(1, 0.75f);
triggerNextAnimation();
}
else if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP)
{
generateAnimation(0.75f, 1);
triggerNextAnimation();
}
return super.onTouchEvent(event);
}
private void generateAnimation(float from, float to)
{
ScaleAnimation scaleAnimation = new ScaleAnimation(from, to, from, to);
scaleAnimation.setFillAfter(true);
scaleAnimation.setDuration(500);
scaleAnimation.setAnimationListener(new ScaleAnimation.AnimationListener()
{
#Override
public void onAnimationStart(Animation animation) { }
#Override
public void onAnimationRepeat(Animation animation) { }
#Override
public void onAnimationEnd(Animation animation)
{
mIsAnimating = false;
triggerNextAnimation();
}
});
mAnimationBuffer.add(scaleAnimation);
}
private void triggerNextAnimation()
{
if (mAnimationBuffer.size() > 0 && !mIsAnimating)
{
mIsAnimating = true;
Animation currAnimation = mAnimationBuffer.get(0);
mAnimationBuffer.remove(0);
startAnimation(currAnimation);
}
}
}
Hope this helps :)
Looks like L Preview adds the ability to specify animations for state changes in the xml file.
https://developer.android.com/preview/material/animations.html#viewstate

Animations in android?

I'm using RotateAnimation and TranslateAnimation in my game project. After completing rotate i'm saving image next using TranslateAnimation. It works fine, but completing rotateanimation the image blinks once.
public void Trainplace(int x,int y,Bitmap b){
param.setMargins(x, y, 0, 0);
Train.setLayoutParams(param);
Train.setImageBitmap(b);
Train.setVisibility(0);
}
public void Trainmove(int x){
TAnimation=new TranslateAnimation(0, 0, 0,x);
TAnimation.setInterpolator(new LinearInterpolator());
TAnimation.setDuration(2000);
TAnimation.setFillAfter(true);
//TAnimation.setFillBefore(true);
Train.startAnimation(TAnimation);
}
public void Trainrotate(int a,int b,int c,int d){
RAnimation=new RotateAnimation(a,b,c,d);
RAnimation.setInterpolator(new LinearInterpolator());
RAnimation.setDuration(2000);
RAnimation.setFillAfter(true);
Train.startAnimation(RAnimation);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.b:
Trainplace(20, 255,bmpy1);
Trainrotate(0,90,50,25);
//Stop button clicks
//B.setEnabled(false);
break;
case R.id.b2:
Trainplace(35, 230,bmpx1);
Trainrotate(0,-90,20,-30);
RAnimation.setAnimationListener(new AnimationListener() {
public void onAnimationStart(Animation arg0) {
}
public void onAnimationRepeat(Animation arg0) {
}
public void onAnimationEnd(Animation arg0) {
int p=0;
while(p<=1){
if(RAnimation.hasEnded()){
Toast.makeText(getApplicationContext(), "End", Toast.LENGTH_SHORT).show();
p=2;
}
}
Trainplace(85, 170,bmpy1);
Trainmove(-100);
}
});
break;
My problem is with completing the rotateanimation - the current image is replaced by another image (ex. my train image rotated 90 degrees then i m placed 90 degrees rotated image in that place )and then move forward by using Translateanimation this is working.
When placing the image inbetween, after completing Rotateanimation and before starting Translateanimation the train image is blinking once.
The same image will place with noproblem (means Translateanimation to Rotateanimation).
When I get blinks in animation, its most probably because the animation listeners 'onAnimationEnd' is firing before the animation is actually done.
You can create av custom view or layout and overide its 'animationEnd' method and add a interface so you can set up a listener.
Here's an example
CustomLayout class
public class CustomLayout extends LinearLayout {
private LayoutAnimationListener mListner;
public CustomLayout(Context context) {
super(context);
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onAnimationEnd() {
super.onAnimationEnd();
if(mListner != null){
mListner.onAnimationEnd(CustomLayout.this);
}
}
#Override
protected void onAnimationStart() {
super.onAnimationStart();
if(mListner != null){
mListner.onAnimationStart(CustomLayout.this);
}
}
public static interface LayoutAnimationListener {
//Notifies the start of the animation.
void onAnimationStart(CustomLayout cLayout);
//Notifies the end of the animation.
void onAnimationEnd(CustomLayout cLayout);
}
public void setLayoutAnimationListener(LayoutAnimationListener listener){
mListner = listener;
}
}
So in your activity you can use it as a normal linearlayout and instead of adding a animation listener to the animation, you can add it to the layout like
com.blessan.CustomLayout layout =
(com.blessan.CustomLayout)findViewById(R.id.counts_layout);
LayoutAnimationListener layoutAnimListener = new LayoutAnimationListener() {
#Override
public void onAnimationStart(CustomLayout cLayout) {
}
#Override
public void onAnimationEnd(CustomLayout cLayout) {
}
};
layout.setLayoutAnimationListener(layoutAnimListener);
This might work for you. Give it a try.

Categories

Resources