編輯:關於Android編程
好久沒寫點東西了,最近看到了一個轉場動畫比較酷炫,今天就來簡單滴分析一下。
先看下今天的效果圖。
分析下效果: 進入詳情頁的時候有共享元素,圓形動畫,文字部分的上移動畫,源碼地址是 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 {
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。
首先要先弄明白兩個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的東西,有興趣的可以去看下源碼。
最後剩下的就請到源碼裡面一探究竟吧(畢竟我也不知道寫什麼了),順便鏈接一些比較好的效果庫
一、示意圖:1)開始畫面:2)游戲中畫面:3)結束畫面:二、分析:1、游戲中的每個元素都可封裝成對象,1)開始按鈕與結束按鈕可封裝成GameButton對象:屬性有:有坐
目前智能電視終端(智能電視和智能電視盒子)已經越來越火,過去主打視頻功能,如今的智能電視終端不僅會繼續完善視頻功能,還會加入電視游戲功能,同時這也趕上了“電視游戲機解禁”
Android 5.0 是 Google 於 2014 年 10 月 15 日(美國太平洋時間)發布的全新 Android 操作系統,英文名為Lollipop,翻譯過來就
一、前言在之前的一篇文章中我們已經詳細介紹了Android中Hook工作的一款神器Xposed工具:Xposed框架原理解析和使用案例分析 在那一篇文章中我們介紹了如何安