我不生成水,只是大自然的搬运工
好久没写点东西了,最近看到了一个转场动画比较酷炫,今天就来简单滴分析一下。
先看下今天的效果图。
分析下效果: 进入详情页的时候有共享元素,圆形动画,文字部分的上移动画,源码地址是 https://github.com/alexjlockwood/adp-activity-transitions。
今天看到的2个类似的库:
https://github.com/naman14/PlayAnimations
https://github.com/hitherejoe/animate
对于5.0的系统要实现转场动画很容易,几句代码分分钟的事情。比如要实现一个简单的转场动画,ActivityA
里面有一个RecylerView
里面的item是一些图片,点击之后跳转到ActivityB
里面去查看图片详情。
在ActivityA
里面的代码如下:
public void gotoDetail2() {
Activity activity = TestActivity.this;
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity,
new Pair(imageView2, DetailActivity.IMAGE_TRANSITION_NAME)
);
Intent intent = new Intent(activity, DetailActivity.class);
intent.putExtra(DetailActivity.EXTRA_IMAGE_URL1, R.mipmap.ic_launcher);
intent.putExtra(DetailActivity.EXTRA_IMAGE_URL2, IMAGE_URL2);
ActivityCompat.startActivity(activity, intent, options.toBundle());
}
在ActivityB
里面的代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
imageView = (ImageView) findViewById(R.id.iv_detail);
int imageResId = getIntent().getIntExtra(EXTRA_IMAGE_URL1, 0);
String imageUrl = getIntent().getStringExtra(EXTRA_IMAGE_URL2);
LGlideUtils.loadImgByUrlForDetail(this, imageUrl, imageView);
// 这里的IMAGE_TRANSITION_NAME需要与跳转进来使用的transitionName一致
ViewCompat.setTransitionName(imageView, IMAGE_TRANSITION_NAME);
}
这样就转场就很容易实现了。。。
但是比如要求在详情界面里面使用的Viewpager
来显示所有的图片,从主页进入到详情页,详情页里面滑动了图片,点击返回键回到主页的时候要求滑动到对应的位置,如果不是很明白请参考qq 里面的消息界面点击图片查看详情,只是qq的返回消息界面的时候判断了一下如果在详情页的图片不在消息界面显示的可见范围内就不向上或者向下滚动而已。。。
其实这个实现也很简单。在详情页里面做下操作:
@Override
public void finishAfterTransition() {
Intent data = new Intent();
data.putExtra("index", 250);
setResult(RESULT_OK, data);
super.finishAfterTransition();
}
这里把当前的图片的索引位置传递给主界面,然后再主界面里面做一些操作:
private Bundle transitionState;
private void testListener() {
setExitSharedElementCallback(new SharedElementCallback() {
@Override
public void onMapSharedElements(List names, Map sharedElements) {
super.onMapSharedElements(names, sharedElements);
if (transitionState != null) {
int index = transitionState.getInt("index", 0);
Log.d(TAG, "onMapSharedElements");
transitionState = null;
}
}
});
}
@Override
public void onActivityReenter(int resultCode, Intent data) {
super.onActivityReenter(resultCode, data);
transitionState = new Bundle(data.getExtras());
Log.d(TAG, "onActivityReenter");
}
在onActivityReenter
里面获取到索引就可以自己去滑动了。。。
简单滴说了下用法,进入今天的主题,现在看下https://github.com/alexjlockwood/adp-activity-transitions里面的代码。
有需要请自己在github上面查看代码,我比较菜那就简单滴分析一下,相当于做个笔记吧。
先看下主界面里面的东西,MainActivity
:
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final boolean DEBUG = false;
static final String EXTRA_STARTING_ALBUM_POSITION = "extra_starting_item_position";
static final String EXTRA_CURRENT_ALBUM_POSITION = "extra_current_item_position";
private RecyclerView mRecyclerView;
private Bundle mTmpReenterState;
private boolean mIsDetailsActivityStarted;
private final SharedElementCallback mCallback = new SharedElementCallback() {
@Override
public void onMapSharedElements(List names, Map sharedElements) {
if (mTmpReenterState != null) {
int startingPosition = mTmpReenterState.getInt(EXTRA_STARTING_ALBUM_POSITION);
int currentPosition = mTmpReenterState.getInt(EXTRA_CURRENT_ALBUM_POSITION);
if (startingPosition != currentPosition) {
// If startingPosition != currentPosition the user must have swiped to a
// different page in the DetailsActivity. We must update the shared element
// so that the correct one falls into place.
String newTransitionName = ALBUM_NAMES[currentPosition];
View newSharedElement = mRecyclerView.findViewWithTag(newTransitionName);
if (newSharedElement != null) {
names.clear();
names.add(newTransitionName);
sharedElements.clear();
sharedElements.put(newTransitionName, newSharedElement);
}
}
mTmpReenterState = null;
} else {
// If mTmpReenterState is null, then the activity is exiting.
View navigationBar = findViewById(android.R.id.navigationBarBackground);
View statusBar = 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);
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setExitSharedElementCallback(mCallback);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new GridLayoutManager(this,
getResources().getInteger(R.integer.activity_main_num_grid_columns)));
mRecyclerView.setAdapter(new CardAdapter());
}
@Override
protected void onResume() {
super.onResume();
mIsDetailsActivityStarted = false;
}
@Override
public void onActivityReenter(int requestCode, Intent data) {
super.onActivityReenter(requestCode, data);
mTmpReenterState = new Bundle(data.getExtras());
int startingPosition = mTmpReenterState.getInt(EXTRA_STARTING_ALBUM_POSITION);
int currentPosition = mTmpReenterState.getInt(EXTRA_CURRENT_ALBUM_POSITION);
if (startingPosition != currentPosition) {
mRecyclerView.scrollToPosition(currentPosition);
}
postponeEnterTransition();
mRecyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mRecyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
// TODO: figure out why it is necessary to request layout here in order to get a smooth transition.
mRecyclerView.requestLayout();
startPostponedEnterTransition();
return true;
}
});
}
private class CardAdapter extends RecyclerView.Adapter<CardHolder> {
private final LayoutInflater mInflater;
public CardAdapter() {
mInflater = LayoutInflater.from(MainActivity.this);
}
@Override
public CardHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
return new CardHolder(mInflater.inflate(R.layout.album_image_card, viewGroup, false));
}
@Override
public void onBindViewHolder(CardHolder holder, int position) {
holder.bind(position);
}
@Override
public int getItemCount() {
return ALBUM_IMAGE_URLS.length;
}
}
private class CardHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private final ImageView mAlbumImage;
private int mAlbumPosition;
public CardHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
mAlbumImage = (ImageView) itemView.findViewById(R.id.main_card_album_image);
}
public void bind(int position) {
Picasso.with(MainActivity.this).load(ALBUM_IMAGE_URLS[position]).into(mAlbumImage);
mAlbumImage.setTransitionName(ALBUM_NAMES[position]);
mAlbumImage.setTag(ALBUM_NAMES[position]);
mAlbumPosition = position;
}
@Override
public void onClick(View v) {
// TODO: is there a way to prevent user from double clicking and starting activity twice?
Intent intent = new Intent(MainActivity.this, DetailsActivity.class);
intent.putExtra(EXTRA_STARTING_ALBUM_POSITION, mAlbumPosition);
if (!mIsDetailsActivityStarted) {
mIsDetailsActivityStarted = true;
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this,
mAlbumImage, mAlbumImage.getTransitionName()).toBundle());
}
}
}
}
代码量不多很简单就是onCreate
里面去setExitSharedElementCallback
,然后再onActivityReenter
里面去获取返回的数据信息在去做mRecyclerView.scrollToPosition
,同时里面用了一下postponeEnterTransition
和startPostponedEnterTransition
以及SharedElementCallback
。
这里参考了下http://blog.csdn.net/h183288132/article/details/51120128
首先要先弄明白两个Activity的转换是分几种情况的:
开始时从A进入B:
1.A退出(exit) ,A中的View播放动画
2.B进入(enter) ,B中的View播放动画
当从B退回A时:
1.B返回(return) ,B中的View播放动画
2.A重新进入(reenter) ,A中的View播放动画
对于SharedElementCallback:
onMapSharedElements
装载共享元素
onSharedElementStart
是共享元素开始时候回调,一般是进入的时候使用
onSharedElementEnd
是共享元素结束的时候回调,一般是退出的时候使用
当然还有其他的方法,具体可以看看文档。
每次进入和退出都会回调SharedElementCallback
对于DetailsActivity
:
package com.alexjlockwood.activity.transitions;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.SharedElementCallback;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v13.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.transition.Slide;
import android.transition.Transition;
import android.transition.TransitionSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.util.List;
import java.util.Map;
import static com.alexjlockwood.activity.transitions.Constants.ALBUM_IMAGE_URLS;
import static com.alexjlockwood.activity.transitions.MainActivity.EXTRA_CURRENT_ALBUM_POSITION;
import static com.alexjlockwood.activity.transitions.MainActivity.EXTRA_STARTING_ALBUM_POSITION;
public class DetailsActivity extends Activity {
private static final String TAG = DetailsActivity.class.getSimpleName();
private static final boolean DEBUG = false;
private static final String STATE_CURRENT_PAGE_POSITION = "state_current_page_position";
private final SharedElementCallback mCallback = new SharedElementCallback() {
@Override
public void onMapSharedElements(List names, Map sharedElements) {
if (mIsReturning) {
ImageView sharedElement = mCurrentDetailsFragment.getAlbumImage();
if (sharedElement == null) {
// If shared element is null, then it has been scrolled off screen and
// no longer visible. In this case we cancel the shared element transition by
// removing the shared element from the shared elements map.
names.clear();
sharedElements.clear();
} else if (mStartingPosition != mCurrentPosition) {
// If the user has swiped to a different ViewPager page, then we need to
// remove the old shared element and replace it with the new shared element
// that should be transitioned instead.
names.clear();
names.add(sharedElement.getTransitionName());
sharedElements.clear();
sharedElements.put(sharedElement.getTransitionName(), sharedElement);
}
}
}
@Override
public void onSharedElementStart(List sharedElementNames, List sharedElements, List sharedElementSnapshots) {
if (!mIsReturning) {
getWindow().setEnterTransition(makeEnterTransition(getSharedElement(sharedElements)));
}
}
private View getSharedElement(List sharedElements) {
for (final View view : sharedElements) {
if (view instanceof ImageView) {
return view;
}
}
return null;
}
};
private Transition makeEnterTransition(View sharedElement) {
View rootView = mCurrentDetailsFragment.getView();
assert rootView != null;
TransitionSet enterTransition = new TransitionSet();
// Play a circular reveal animation starting beneath the shared element.
Transition circularReveal = new CircularReveal(sharedElement);
circularReveal.addTarget(rootView.findViewById(R.id.details_header_container));
enterTransition.addTransition(circularReveal);
// Slide the cards in through the bottom of the screen.
Transition cardSlide = new Slide(Gravity.BOTTOM);
cardSlide.addTarget(rootView.findViewById(R.id.details_text_container));
enterTransition.addTransition(cardSlide);
final ImageView backgroundImage = (ImageView) rootView.findViewById(R.id.details_background_image);
backgroundImage.setAlpha(0f);
enterTransition.addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
// backgroundImage.animate().alpha(1f).setDuration(2000);
}
});
enterTransition.setDuration(400);
return enterTransition;
}
private DetailsFragment mCurrentDetailsFragment;
private int mCurrentPosition;
private int mStartingPosition;
private boolean mIsReturning;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
postponeEnterTransition();
setEnterSharedElementCallback(mCallback);
mStartingPosition = getIntent().getIntExtra(EXTRA_STARTING_ALBUM_POSITION, 0);
if (savedInstanceState == null) {
mCurrentPosition = mStartingPosition;
} else {
mCurrentPosition = savedInstanceState.getInt(STATE_CURRENT_PAGE_POSITION);
}
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(new DetailsFragmentPagerAdapter(getFragmentManager()));
pager.setCurrentItem(mCurrentPosition);
pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
mCurrentPosition = position;
}
});
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_CURRENT_PAGE_POSITION, mCurrentPosition);
}
@Override
public void finishAfterTransition() {
mIsReturning = true;
Intent data = new Intent();
data.putExtra(EXTRA_STARTING_ALBUM_POSITION, mStartingPosition);
data.putExtra(EXTRA_CURRENT_ALBUM_POSITION, mCurrentPosition);
setResult(RESULT_OK, data);
super.finishAfterTransition();
}
private class DetailsFragmentPagerAdapter extends FragmentStatePagerAdapter {
public DetailsFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return DetailsFragment.newInstance(position, mStartingPosition);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
mCurrentDetailsFragment = (DetailsFragment) object;
}
@Override
public int getCount() {
return ALBUM_IMAGE_URLS.length;
}
}
}
这里注意在onSharedElementStart
里面使用了makeEnterTransition
动画里面加了进入的时候的圆形动画和文字上移动画,这个用transition
的代码形式可以简单的实现。。。
如果看过转场动画源码的大神都知道其实ShareElements 里面调用还是transition
,然后transition
里面主要使用了ViewOverlay
的东西,有兴趣的可以去看下源码。
最后剩下的就请到源码里面一探究竟吧(毕竟我也不知道写什么了),顺便链接一些比较好的效果库