本文主要講如何一步一步實(shí)現(xiàn)ImageView共享元素轉(zhuǎn)場(chǎng)動(dòng)畫禀忆,按照國(guó)際慣例绰播,先上效果圖:
實(shí)現(xiàn)思路一:
這里假設(shè)從Activity A跳轉(zhuǎn)到Activity B国拇,共享的元素是ImageView:
在Activity A中獲得共享a(ImageView)的位置信息,在跳轉(zhuǎn)時(shí)硼婿,移除Activity默認(rèn)的轉(zhuǎn)場(chǎng)動(dòng)畫并將a的位置信息傳給B怒见;
在B中接收a的位置信息俗慈,獲得B中的目標(biāo)b(ImageView)在布局中的位置;到這里就獲得了a和b兩個(gè)imageView的位置信息了遣耍;
改變b的位置和大小闺阱,使b完全和a位置重合;
對(duì)改變后的b進(jìn)行動(dòng)畫變換配阵,讓b恢復(fù)到改變前
注意:關(guān)于共享元素轉(zhuǎn)場(chǎng)馏颂,這里并不一定需要Activity B是背景透明的;如果Activity B是一個(gè)全屏顯示ImageView的activity棋傍,那么背景透明的情況下感覺(jué)體驗(yàn)稍微好一點(diǎn)救拉;但如果Activity B是一個(gè)含多控件的activity,則并沒(méi)有什么區(qū)別瘫拣。
Activity A的布局和源碼
A布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="200dp"
android:layout_height="120dp"
android:scaleType="centerInside"
android:src="@drawable/ic_one_piece"/>
</RelativeLayout>
為了方便闡述亿絮,這里的ImageView固定尺寸大小,固定使用scaleType為centerInside,引用的是本地資源,下文會(huì)詳細(xì)說(shuō)明這些的派昧。圖片ic_one_piece如下:
A源碼:
//設(shè)置顯示的圖片
final ImageView imageView = (ImageView)findViewById(R.id.iv_icon);
findViewById(R.id.iv_icon).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(RectActivity.this,RectDetailActivity.class);
//創(chuàng)建一個(gè)Rect,保存當(dāng)前imageView的位置信息
Rect rect = new Rect();
//將位置信息賦給rect
imageView.getGlobalVisibleRect(rect);
//將位置信息賦給intent
intent.setSourceBounds(rect);
//activity跳轉(zhuǎn)
startActivity(intent);
//屏蔽activity跳轉(zhuǎn)的默認(rèn)轉(zhuǎn)場(chǎng)效果
overridePendingTransition(0,0);
}
});
使用Rect保存ImageView的位置信息黔姜,并傳遞給Activity B,記得屏蔽activity A的默認(rèn)轉(zhuǎn)場(chǎng)效果.
Activity B的布局和源碼
B布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_icon"
android:layout_gravity="center_vertical"
android:scaleType="centerInside"
android:layout_width="300dp"
android:layout_height="180dp"/>
</FrameLayout>
B布局中的ImageView的scaleType和A中的一致,圖片的寬高比例也一致蒂萎;
B源碼:
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.example.demoshareimageview.R;
public class RectDetailActivity extends AppCompatActivity {
private ImageView mImageView;
//動(dòng)畫時(shí)間
public static final int DURATION = 300;
//動(dòng)畫插值器
private static final AccelerateDecelerateInterpolator DEFAULT_INTERPOLATOR = new AccelerateDecelerateInterpolator();
//上一個(gè)界面的圖片位置信息
private Rect mSourceRect;
//上一個(gè)界面的圖片的寬度
private int mSourceWidth;
//上一個(gè)界面的圖片的高度
private int mSourceHeight;
//當(dāng)前界面的目標(biāo)圖片的位置
private Rect mTargetRect = new Rect();
//前后兩個(gè)圖片的收縮比
private float mScaleWidth, mScaleHeight;
//前后兩個(gè)圖片的位移距離
private float mTransitionX, mTransitionY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rect_detail);
//初始化控件
initView();
//初始化場(chǎng)景
initBehavior();
}
/**
* 初始化控件
*/
private void initView(){
mImageView = (ImageView)findViewById(R.id.iv_icon);
//設(shè)置圖片,也可以直接在xml布局中設(shè)置:android:src="@drawable/ic_one_piece"
mImageView.setImageResource(R.drawable.ic_one_piece);
}
/**
* 初始化場(chǎng)景
*/
private void initBehavior(){
//獲取上一個(gè)界面?zhèn)鬟^(guò)來(lái)的Rect
mSourceRect = getIntent().getSourceBounds();
//計(jì)算上一個(gè)界面圖片的寬度和高度
mSourceWidth = mSourceRect.right-mSourceRect.left;
mSourceHeight = mSourceRect.bottom-mSourceRect.top;
//當(dāng)界面的imageView測(cè)量完成后秆吵,即高度和寬度確定后
mImageView.post(new Runnable() {
@Override
public void run() {
//獲取目標(biāo)imageView在布局中的位置
mImageView.getGlobalVisibleRect(mTargetRect);
//更改mImageView的位置,使其和上一個(gè)界面的圖片的位置重合
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(mSourceWidth,mSourceHeight);
params.setMargins(mSourceRect.left,mSourceRect.top-getStatusBarHeight()-getActionBarHeight(),
mSourceRect.right,mSourceRect.bottom);
mImageView.setLayoutParams(params);
//計(jì)算圖片縮放比例和位移
calculateInfo();
// 設(shè)置入場(chǎng)動(dòng)畫
runEnterAnim();
}
});
}
/**
* 獲取狀態(tài)欄高度
* @return
*/
private int getStatusBarHeight() {
//獲取status_bar_height資源的ID
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
//根據(jù)資源ID獲取響應(yīng)的尺寸值
return getResources().getDimensionPixelSize(resourceId);
}
return -1;
}
/**獲取ActionBar高度
* @return
*/
private int getActionBarHeight(){
//如果有ActionBar
if(getSupportActionBar()!=null){
return getSupportActionBar().getHeight();
}
return 0;
}
/**
* 計(jì)算圖片縮放比例五慈,以及位移距離
*
*/
private void calculateInfo() {
// 計(jì)算目標(biāo)imageView的寬高
int targetWidth = mTargetRect.right - mTargetRect.left;
int targetHeight = mTargetRect.bottom - mTargetRect.top;
//獲得收縮比
mScaleWidth = (float) targetWidth / mSourceWidth;
mScaleHeight = (float) targetHeight / mSourceHeight;
//x,y上的位移
mTransitionX = (mTargetRect.left+(mTargetRect.right - mTargetRect.left) / 2)
- (mSourceRect.left + (mSourceRect.right - mSourceRect.left) / 2);
mTransitionY = (mTargetRect.top + (mTargetRect.bottom - mTargetRect.top) / 2)
- (mSourceRect.top + (mSourceRect.bottom - mSourceRect.top) / 2);
}
/**
* 入場(chǎng)動(dòng)畫:屬性動(dòng)畫
*/
private void runEnterAnim() {
mImageView.animate()
.setInterpolator(DEFAULT_INTERPOLATOR)
.setDuration(DURATION)
.scaleX(mScaleWidth)
.scaleY(mScaleHeight)
.translationX(mTransitionX)
.translationY(mTransitionY)
//withEndAction要求版本在16以上
.withEndAction(new Runnable() {
@Override
public void run() {
}
})
.start();
}
/**
* 退場(chǎng)動(dòng)畫:屬性動(dòng)畫
*/
private void runExitAnim() {
mImageView.animate()
.setInterpolator(DEFAULT_INTERPOLATOR)
.setDuration(DURATION)
.scaleX(1)
.scaleY(1)
.translationX(0)
.translationY(0)
//withEndAction要求版本在16以上
.withEndAction(new Runnable() {
@Override
public void run() {
finish();
overridePendingTransition(0, 0);
}
})
.start();
}
@Override
public void onBackPressed() {
// 使用退場(chǎng)動(dòng)畫
runExitAnim();
}
}
首先從intent拿到A中imageView的位置信息mSourceRect纳寂,在B的imageView測(cè)量完成后,用mTargetRect保存泻拦,然后更改mImageView的位置毙芜,使其和A圖片的位置重合;對(duì)比mSourceRect和mTargetRect,計(jì)算出imageView的縮放比例和位移距離争拐,最后執(zhí)行入場(chǎng)動(dòng)畫腋粥。當(dāng)退出Activity時(shí),執(zhí)行退場(chǎng)動(dòng)畫架曹,記得屏蔽B退出的默認(rèn)轉(zhuǎn)場(chǎng)動(dòng)畫隘冲。到這里,一個(gè)初步的共享元素轉(zhuǎn)場(chǎng)動(dòng)畫就完成了音瓷。效果如下:
在上面的操作中对嚼,實(shí)際上是對(duì)兩個(gè)ImageView控件的轉(zhuǎn)場(chǎng)動(dòng)畫夹抗,并不是對(duì)兩個(gè)ImageView中圖片的轉(zhuǎn)場(chǎng)動(dòng)畫绳慎。由activity A和B的布局文件中看出imageView的大小是固定并且比例是“算好”的,目的就是使ImageView中的圖片能基本填充滿整個(gè)ImageView控件漠烧,從而在對(duì)控件作轉(zhuǎn)場(chǎng)時(shí)產(chǎn)生期望的效果杏愤。當(dāng)將B布局中ImageView的高度設(shè)為“400dp”時(shí),效果如下:
可以看到最終的imageView被拉伸了已脓,因?yàn)樗皇怯汕耙粋€(gè)imageView的放大拉伸得到的珊楼,顯示的內(nèi)容是一模一樣的,但很多時(shí)候度液,小圖和大圖并非一模一樣的厕宗,可能小圖所顯示的圖片是被截掉一部分的,而大圖則是完全顯示出來(lái)的堕担;除此之外已慢,還有一個(gè)問(wèn)題:在一般的應(yīng)用場(chǎng)景中,ImageView的圖片常常不會(huì)鋪滿整個(gè)控件的霹购,上面的方法中佑惠,作縮放時(shí)是以ImageView控件的寬高為比例,這是不對(duì)的,正確的應(yīng)該是以imageView內(nèi)實(shí)際繪制的圖片的寬高為比例膜楷。我們接著改進(jìn)一下:
由于我們需要知道ImageView內(nèi)實(shí)際繪制圖片的寬高旭咽,所以需要拿到activity A的實(shí)際圖片寬高。保持activity A的布局不變赌厅,修改A源碼穷绵。
A源碼:
private int mSourceDrawableWidth;
private int mSourceDrawableHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drawable);
//設(shè)置顯示的圖片
final ImageView imageView = (ImageView)findViewById(R.id.iv_icon);
findViewById(R.id.iv_icon).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(DrawableActivity.this,DrawableDetailActivity.class);
//創(chuàng)建一個(gè)Rect,保存當(dāng)前imageView的位置信息
Rect rect = new Rect();
//將位置信息賦給rect
imageView.getGlobalVisibleRect(rect);
//將位置信息賦給intent
intent.setSourceBounds(rect);
//計(jì)算imageView中顯示圖片的實(shí)際繪制的寬高
calDrawableWidthAndHeight(imageView);
intent.putExtra("sourceDrawableWidth", mSourceDrawableWidth);
intent.putExtra("sourceDrawableHeight", mSourceDrawableHeight);
//activity跳轉(zhuǎn)
startActivity(intent);
//屏蔽activity跳轉(zhuǎn)的默認(rèn)轉(zhuǎn)場(chǎng)效果
overridePendingTransition(0,0);
}
});
}
/**計(jì)算imageView中顯示圖片的實(shí)際繪制的寬高
* @param imageView
*/
private void calDrawableWidthAndHeight(ImageView imageView){
Drawable imgDrawable = imageView.getDrawable();
if (imgDrawable != null) {
//獲得ImageView中Image的真實(shí)寬高,
int dw = imageView.getDrawable().getBounds().width();
int dh = imageView.getDrawable().getBounds().height();
//獲得ImageView中Image的變換矩陣
Matrix m = imageView.getImageMatrix();
float[] values = new float[10];
m.getValues(values);
//Image在繪制過(guò)程中的變換矩陣特愿,從中獲得x和y方向的縮放系數(shù)
float sx = values[0];
float sy = values[4];
//計(jì)算Image在屏幕上實(shí)際繪制的寬高
mSourceDrawableWidth = (int) (dw * sx);
mSourceDrawableHeight = (int) (dh * sy);
}
}
這里多做一個(gè)事情:獲取ImageView內(nèi)的實(shí)際繪制圖片的寬高并傳遞給activity B请垛。
B布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll_rootView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_icon"
android:visibility="invisible"
android:layout_gravity="center_vertical"
android:scaleType="centerInside"
android:layout_width="300dp"
android:layout_height="180dp"/>
</FrameLayout>
稍微修改B布局,將imageView設(shè)為不可見(jiàn)洽议。注意宗收,這里要使用android:visibility="invisible",而不是android:visibility="gone",因?yàn)橐谶\(yùn)行過(guò)程中測(cè)量imageView的寬高亚兄,設(shè)為gone就拿不到寬高了混稽。
B源碼:
public class DrawableDetailActivity extends AppCompatActivity {
private FrameLayout llRootView;
private ImageView mImageView;
private ImageView mTempImageView;
//動(dòng)畫時(shí)間
public static final int DURATION = 300;
//動(dòng)畫插值器
private static final AccelerateDecelerateInterpolator DEFAULT_INTERPOLATOR = new AccelerateDecelerateInterpolator();
//上一個(gè)界面的圖片位置信息
private Rect mSourceRect;
//上一個(gè)界面的imageView的寬度
private int mSourceWidth;
//上一個(gè)界面的imageView的高度
private int mSourceHeight;
//上一個(gè)界面,imageView內(nèi)容繪制圖片的實(shí)際寬度
private int mSourceDrawableWidth;
//上一個(gè)界面审胚,imageView內(nèi)容繪制圖片的實(shí)際高度
private int mSourceDrawableHeight;
//當(dāng)前界面的目標(biāo)圖片的位置
private Rect mTargetRect = new Rect();
//前后兩個(gè)圖片的收縮比
private float mScaleWidth, mScaleHeight;
//前后兩個(gè)圖片的位移距離
private float mTransitionX, mTransitionY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drawable_detail);
//初始化控件
initView();
//初始化場(chǎng)景
initBehavior();
}
/**
* 初始化控件
*/
private void initView(){
llRootView = (FrameLayout)findViewById(R.id.ll_rootView);
mImageView = (ImageView)findViewById(R.id.iv_icon);
//設(shè)置圖片,也可以直接在xml布局中設(shè)置:android:src="@drawable/ic_one_piece"
mImageView.setImageResource(R.drawable.ic_one_piece);
}
/**
* 初始化場(chǎng)景
*/
private void initBehavior(){
//獲取上一個(gè)界面?zhèn)鬟^(guò)來(lái)的Rect
mSourceRect = getIntent().getSourceBounds();
//計(jì)算上一個(gè)界面圖片的寬度和高度
mSourceWidth = mSourceRect.right-mSourceRect.left;
mSourceHeight = mSourceRect.bottom-mSourceRect.top;
mSourceDrawableWidth = getIntent().getIntExtra("sourceDrawableWidth",0);
mSourceDrawableHeight = getIntent().getIntExtra("sourceDrawableHeight",0);
//當(dāng)界面的imageView測(cè)量完成后匈勋,即高度和寬度確定后
mImageView.post(new Runnable() {
@Override
public void run() {
//獲取目標(biāo)imageView在布局中的位置
mImageView.getGlobalVisibleRect(mTargetRect);
mTempImageView = new ImageView(DrawableDetailActivity.this);
mTempImageView.setImageResource(R.drawable.ic_one_piece);
mTempImageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
//更改mTempImageView的位置,使其和上一個(gè)界面的圖片的位置重合
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(mSourceWidth,mSourceHeight);
params.setMargins(mSourceRect.left,mSourceRect.top-getStatusBarHeight()-getActionBarHeight(),mSourceRect.right,mSourceRect.bottom);
mTempImageView.setLayoutParams(params);
//把view添加進(jìn)來(lái)
llRootView.addView(mTempImageView);
//計(jì)算圖片縮放比例和位移
calculateInfo();
// 設(shè)置入場(chǎng)動(dòng)畫
runEnterAnim();
}
});
}
/**
* 獲取狀態(tài)欄高度
* @return
*/
private int getStatusBarHeight() {
//獲取status_bar_height資源的ID
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
//根據(jù)資源ID獲取響應(yīng)的尺寸值
return getResources().getDimensionPixelSize(resourceId);
}
return -1;
}
/**獲取ActionBar高度
* @return
*/
private int getActionBarHeight(){
//如果有ActionBar
if(getSupportActionBar()!=null){
return getSupportActionBar().getHeight();
}
return 0;
}
/**
* 計(jì)算圖片縮放比例膳叨,以及位移距離
*
*/
private void calculateInfo() {
// 計(jì)算目標(biāo)imageView的中實(shí)際繪制圖片的寬高
int targetDrawableWidth = 0;
int targetDrawableHeight = 0;
Drawable imgDrawable = mImageView.getDrawable();
if (imgDrawable != null) {
//獲得ImageView中Image的真實(shí)寬高洽洁,
int dw = mImageView.getDrawable().getBounds().width();
int dh = mImageView.getDrawable().getBounds().height();
//獲得ImageView中Image的變換矩陣
Matrix m = mImageView.getImageMatrix();
float[] values = new float[10];
m.getValues(values);
//Image在繪制過(guò)程中的變換矩陣,從中獲得x和y方向的縮放系數(shù)
float sx = values[0];
float sy = values[4];
//計(jì)算Image在屏幕上實(shí)際繪制的寬高
targetDrawableWidth = (int) (dw * sx);
targetDrawableHeight = (int) (dh * sy);
}
//獲得收縮比
mScaleWidth = (float) targetDrawableWidth / mSourceDrawableWidth;
mScaleHeight = (float) targetDrawableHeight / mSourceDrawableHeight;
//x,y上的位移
mTransitionX = (mTargetRect.left+(mTargetRect.right - mTargetRect.left) / 2)
- (mSourceRect.left + (mSourceRect.right - mSourceRect.left) / 2);
mTransitionY = (mTargetRect.top + (mTargetRect.bottom - mTargetRect.top) / 2)
- (mSourceRect.top + (mSourceRect.bottom - mSourceRect.top) / 2);
}
/**
* 入場(chǎng)動(dòng)畫:屬性動(dòng)畫
*/
private void runEnterAnim() {
mTempImageView.animate()
.setInterpolator(DEFAULT_INTERPOLATOR)
.setDuration(DURATION)
.scaleX(mScaleWidth)
.scaleY(mScaleHeight)
.translationX(mTransitionX)
.translationY(mTransitionY)
.withEndAction(new Runnable() {
@Override
public void run() {
mTempImageView.setVisibility(View.INVISIBLE);
mImageView.setVisibility(View.VISIBLE);
}
})
.start();
}
/**
* 退場(chǎng)動(dòng)畫:屬性動(dòng)畫
*/
private void runExitAnim() {
if(mTempImageView==null){
finish();
return;
}
mImageView.setVisibility(View.INVISIBLE);
mTempImageView.setVisibility(View.VISIBLE);
mTempImageView.animate()
.setInterpolator(DEFAULT_INTERPOLATOR)
.setDuration(DURATION)
.scaleX(1)
.scaleY(1)
.translationX(0)
.translationY(0)
//withEndAction要求版本在16以上
.withEndAction(new Runnable() {
@Override
public void run() {
finish();
overridePendingTransition(0, 0);
}
})
.start();
}
@Override
public void onBackPressed() {
// 使用退場(chǎng)動(dòng)畫
runExitAnim();
}
}
這里的思路和第一種思路不一樣的地方是:在activity B中拿到上一個(gè)imageView的信息后菲嘴,創(chuàng)建一個(gè)臨時(shí)的mTempImageView饿自,并將這個(gè)view覆蓋原位置,再拿這個(gè)view做轉(zhuǎn)場(chǎng)動(dòng)畫龄坪,動(dòng)畫完成后昭雌,隱藏這個(gè)臨時(shí)的imageView,顯示實(shí)際在布局中編寫的目標(biāo)ImageView刨疼;當(dāng)退出activity B時(shí)叙量,也是使用臨時(shí)imageView實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫。現(xiàn)在無(wú)論我們?nèi)绾卧O(shè)置imageView的位置和寬高比例缝彬,都能達(dá)到期望的效果了妓局,如下:
現(xiàn)在基本能達(dá)到期望效果了总放,那么問(wèn)題來(lái)了,在上述的操作中好爬,ImageView的ScaleType都是centerInside局雄,如果前后兩個(gè)activity中ImageView的ScaleType不一樣呢?假設(shè)將activity A中的ScaleType設(shè)置為centerCrop抵拘,ImageView的寬度從180dp設(shè)置為100dp哎榴,達(dá)到A中圖片顯示不全的目的型豁;activity B中的ScaleType設(shè)置為centerInside(注意:臨時(shí)創(chuàng)建的mTempImageView也需要設(shè)置為centerCrop),將動(dòng)畫時(shí)間延遲到800尚蝌,看到的效果如下:
可以看到在動(dòng)畫結(jié)束的時(shí)候迎变,圖片突然從顯示不全變成完整顯示,顯得較為突兀飘言。對(duì)于這種情況衣形,我的做法是縮短動(dòng)畫時(shí)間,比如說(shuō)縮短到100姿鸿,讓視覺(jué)上不容易觀察到突變谆吴。
更多
上面所描述的情況都是基于本地資源圖片的,對(duì)于網(wǎng)絡(luò)請(qǐng)求的圖片該如何處理呢苛预,如何實(shí)現(xiàn)從列表activity到列表activity的共享轉(zhuǎn)場(chǎng)呢句狼?限于篇幅,下篇文章再說(shuō)...