Android 共享元素效果

  • Android 5.0 以上使用 Transition 實現(xiàn)的方法
  • Android 5.0 以下的實現(xiàn)方法

Transition

Transition 框架是 Android 4.4 KitKat 中加入的,但在 5.0 才開始被人應(yīng)用起來,
而且這一部分也涉及了 22.0 的 API,雖然有對應(yīng)的 support.v4 包,但也還是有點問題。
所以這一部分可以說是 5.0 以上適用的方法。

效果(錄制出來的效果有點卡頓):

共享元素效果圖
  • 設(shè)置 Activity 引用的 theme
    設(shè)置 windowContentTransitions 為 true,即開啟窗口內(nèi)容過渡
<style name="AppTheme.custom">
      <item name="colorControlHighlight">@color/ControlHighlight</item>
      <item name="android:windowIsTranslucent">true</item>
      <item name="android:windowContentTransitions">true</item>
</style>

這里遇到一點小問題,即上述 Activity 引用的 style 中不僅設(shè)置了 android:windowIsTranslucent,也設(shè)置了 android:windowIsTranslucent : 讓 Activity 的背景為透明,在我測試的時候發(fā)現(xiàn)使用共享元素的時候出現(xiàn)了返回時閃屏的現(xiàn)象,解決方法是設(shè)置 Activity 背景顏色為透明。
onCreate 中:

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getActivity().getWindow().setBackgroundDrawableResource(R.color.transparent);
}

或者在上述的 style 中的 theme 添加:

<item name="android:windowBackground">@android:color/transparent</item>
  • 設(shè)置共享元素
    其實實現(xiàn)這樣的效果就是把第一個界面的 ImageView 移動、放大到第二個界面的 ImageView 的位置,借助 API 實現(xiàn)效果可以省去自己寫動畫的邏輯,但就需要讓系統(tǒng)關(guān)聯(lián)兩個 View。
    而關(guān)聯(lián)兩個 View 通過設(shè)置 android:transitionName 屬性。
    首先在第一個界面的 activity_main.xml
<LinearLayout>
      <ImageView
          android:id="@+id/img"
          android:transitionName="testImg"
          android:scaleType="centerCrop"
          android:layout_width="match_parent"
          android:layout_height="160dp" />
      ...
</LinearLayout>

在打開的 Activity 的 xml 中

<LinearLayout>
      <ImageView
          android:id="@+id/item_img"
          android:transitionName="testImg"
          android:scaleType="centerCrop"
          android:layout_width="match_parent"
          android:layout_height="380dp" />
      ...
</LinearLayout>

對應(yīng)的 ImageView 中的android:transitionName屬性值必須相同,而對兩個控件的大小、id 等屬性并無要求。

  • 第一個 Activity 中,使用共享元素啟動新界面方法
    MainActivity:
Intent intent = new Intent(getActivity(),SecondActivity.class);
ActivityOptionsCompat options = ActivityOptionsCompat
        .makeSceneTransitionAnimation(getActivity(),
                mImgView,"testImg");
startActivity(intent,options.toBundle());

makeSceneTransitionAnimation 傳入的參數(shù)中,mImgView 是第一個界面中 ImageView 的實例,第三個參數(shù)對應(yīng) xml 中的 android:transitionName 的值。

  • 在被打開的 Activity 中
    首先加載圖片還是要自己寫的,其余的需要注意:
    返回不再調(diào)用finishActivity() 而是 supportFinishAfterTransition()
@Override
public void onBackPressed() {
    supportFinishAfterTransition();
}

因為打開新的 Activity 的時候,可能要去加載新的圖片,這時候我們需要延遲過渡動畫的開始,直到圖片加載完成之后再開始動畫。否則會出現(xiàn)各種 bug。
所以要在第二個 Activity 中的 onCreate() 中阻止動畫的執(zhí)行:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // 延遲共享動畫的執(zhí)行
    postponeEnterTransition();
}

然后在圖片加載完成后開始動畫:

Glide.with(this)
        .load(data.getImage())
        .priority(Priority.HIGH)
        .diskCacheStrategy(DiskCacheStrategy.ALL)
        .into(new GlideDrawableImageViewTarget(mImageView){
            @Override
            public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
                super.onResourceReady(resource, animation);
                //圖片加載完成的回調(diào)中,啟動過渡動畫
                supportStartPostponedEnterTransition();
            }
        });

當然,啟動動畫不一定要等待圖片加載完成再進行,因為還存在著圖片加載失敗、加載時間過長等問題,這里只是提出一種方法,實際還是自己看情況決定。

以上只是簡單的實現(xiàn)了一種效果,關(guān)于 Transition 的使用、共享元素在
Fragment 中的使用、多個共享元素的使用等,在這里暫時不打算細講,可以參考:
使用 Transition FrameWork 實現(xiàn)有意義的轉(zhuǎn)場動畫(譯)
(譯)Android 5.0 頁面共享元素過渡
定義定制動畫

利用動畫效果實現(xiàn)

  • 效果:
Android 5.0 以下兼容共享元素效果圖
  • 設(shè)置 Activity 背景透明
<style name="AppTheme.custom">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>
  • 創(chuàng)建一個實體類以存儲 ImageView 的位置數(shù)據(jù)并實現(xiàn) Parcelable 接口,
    位置數(shù)據(jù)包括控件距離父控件( 屏幕 )左邊、頂部的距離和控件的寬高。
public static class ImageBean implements Parcelable {

    private String FilePath;
    private String FileName;
    private Boolean IsSelect;
    private int viewLeft;
    private int viewTop;
    private int viewHeight;
    private int viewWidth;

    public int getViewLeft() {
        return viewLeft;
    }
    public void setViewLeft(int viewLeft) {
        this.viewLeft = viewLeft;
    }
    public int getViewTop() {
        return viewTop;
    }
    public void setViewTop(int viewTop) {
        this.viewTop = viewTop;
    }
    public int getViewHeight() {
        return viewHeight;
    }
    public void setViewHeight(int viewHeight) {
        this.viewHeight = viewHeight;
    }
    public int getViewWidth() {
        return viewWidth;
    }
    public void setViewWidth(int viewWidth) {
        this.viewWidth = viewWidth;
    }
    public ImageBean(int left,int top,int height,int width){
        viewLeft = left;
        viewTop = top;
        viewHeight = height;
        viewWidth = width;
    }
    public ImageBean(String p, String f){
        FilePath = p;
        FileName = f;
        IsSelect = false;
    }
    public String getFilePath() {
        return FilePath;
    }
    public void setFilePath(String filePath) {
        FilePath = filePath;
    }
    public String getFileName() {
        return FileName;
    }
    public void setFileName(String fileName) {
        FileName = fileName;
    }
    public Boolean getSelect() {
        return IsSelect;
    }
    public void setSelect(Boolean select) {
        IsSelect = select;
    }
    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.FilePath);
        dest.writeString(this.FileName);
        dest.writeValue(this.IsSelect);
        dest.writeInt(this.viewLeft);
        dest.writeInt(this.viewTop);
        dest.writeInt(this.viewHeight);
        dest.writeInt(this.viewWidth);
    }
    protected ImageBean(Parcel in) {
        this.FilePath = in.readString();
        this.FileName = in.readString();
        this.IsSelect = (Boolean) in.readValue(Boolean.class.getClassLoader());
        this.viewLeft = in.readInt();
        this.viewTop = in.readInt();
        this.viewHeight = in.readInt();
        this.viewWidth = in.readInt();
    }
    public static final Parcelable.Creator<ImageBean> CREATOR = new Parcelable.Creator<ImageBean>() {
        @Override
        public ImageBean createFromParcel(Parcel source) {
            return new ImageBean(source);
        }
        @Override
        public ImageBean[] newArray(int size) {
            return new ImageBean[size];
        }
    };
}
  • 在打開新的 Activity 前,獲取點擊的 ImageView 的位置數(shù)據(jù)
 private ImageBean img2Location(ImageView imageView,String path){

    int[] location = new int[2];
    imageView.getLocationOnScreen(location);
    ImageBean bean = new ImageBean(
            location[0],location[1],
            imageView.getHeight(),imageView.getWidth());
    bean.setFilePath(path);

    return bean;
}
  • 通過 Intent 將存有位置數(shù)據(jù)的 ImageBean 實例傳到新的 Activity
String path = "..";
ImageView imageView = ...;
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("location",img2Location(imageView,path));
startActivity(intent);
  • 在打開的 Activity 中,通過傳過來的 path 數(shù)據(jù)加載圖片,
    同時獲取傳過來的位置數(shù)據(jù)。
public void initView(@Nullable final View view) {
    getActivity().setTheme(R.style.translucent);
    Glide.with(getActivity()).load(bean.getFilePath())
            .placeholder(R.drawable.black_place_holder).into(mPhotoView);    
    //獲取位置數(shù)據(jù)
    Intent intent = getIntent();
    ImageBean bean = intent.getParcelableExtra("image");
    int mOriginLeft = bean.getViewLeft();
    int mOriginTop = bean.getViewTop();
    int mOriginHeight = bean.getViewHeight();
    int mOriginWidth = bean.getViewWidth();
}
  • 監(jiān)聽返回按鈕,點擊時開始移動、縮放的動畫,
    動畫用 ValueAnimator 和 ScaleAnimation 實現(xiàn)。
private void finishPhotoViewWithTap(int left,int top){

    //動畫的執(zhí)行時間
    int ANIMATOR_DURATION = 500;

    //控制 x 軸上的移動,將 ImageView 移動到對應(yīng)的位置
    ValueAnimator translateXAnimator = ValueAnimator.ofFloat(0, left);
    translateXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            //用 setX 移動控件
            mPhotoView.setX((Float) valueAnimator.getAnimatedValue());
        }
    });
    translateXAnimator.setDuration(ANIMATOR_DURATION);
    translateXAnimator.start();

    ///控制 y 軸上的移動,并監(jiān)聽動畫的結(jié)束,動畫結(jié)束時關(guān)閉該 Activity
    ValueAnimator translateYAnimator = ValueAnimator.ofFloat(0, top);
    translateYAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            mPhotoView.setY((Float) valueAnimator.getAnimatedValue());
        }
    });
    translateYAnimator.setDuration(ANIMATOR_DURATION);
    translateYAnimator.start();
    translateXAnimator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationEnd(Animator animation) {
            //動畫結(jié)束的回調(diào)
            animation.removeAllListeners();
            finishActivity();
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    });

    //控制縮放的 Animation
    Animation scaleAnimation = new ScaleAnimation(1.0f,0.2f,1.0f,0.2f
            ,left,top);
    //控制透明度的 Animation
    Animation alphaAnimation = new AlphaAnimation(1.0f,0f);

    //用 AnimationSet 將以上兩個 Animation 結(jié)合到一起
    AnimationSet set = new AnimationSet(true);
    set.setDuration(ANIMATOR_DURATION);
    set.setFillAfter(true);
    set.addAnimation(scaleAnimation);
    set.addAnimation(alphaAnimation);
    mPhotoView.startAnimation(set);
}

原理大概就是如此,實現(xiàn)動畫的方法有很多種。
可以參考:
Activity 共享元素轉(zhuǎn)場動畫實踐
Android共享元素轉(zhuǎn)場動畫兼容實踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容