Android高階轉(zhuǎn)場動畫-ShareElement完全攻略

本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨(dú)家發(fā)布

看完本文你能學(xué)到什么:

1萄唇、ShareElement是什么以及基本用法
2、理解ShareElement是如何運(yùn)作的
3诸狭、掌握ShareElement的進(jìn)階用法(Fresco、Glide、RecyclerView&ViewPager圖片視頻混合的情況下如何實(shí)現(xiàn)ShareElement動畫)
4戏溺、一個封裝好可以簡單實(shí)現(xiàn)以上ShareElement動畫的開源庫 YcShareElement(https://github.com/yellowcath/YcShareElement)

[TOC]

什么是ShareElement

ShareElement即兩個Activity(或Fragment)之間切換時的共享元素渣蜗,如下圖,可以看到旷祸,選中的聯(lián)系人頭像和名字直接很自然地過渡到了下一頁的位置耕拷,這兩個就是本次切換動畫的ShareElement

ContactsAnim.gif

ShareElement這一套也能實(shí)現(xiàn)同一個Activity(Fragment)內(nèi)部的復(fù)雜切換動畫,不過因?yàn)樵贏ctivity內(nèi)部做動畫有太多現(xiàn)成的手段托享,所以本文不涉及這方面內(nèi)容

ShareElement應(yīng)用場景

以我個人的觀點(diǎn)骚烧,ShareElement最好的應(yīng)用場景之一就是現(xiàn)在的以圖片、視頻為主的內(nèi)容流APP嫌吠。下面是我司應(yīng)用了ShareElement的app與某app的用戶瀏覽體驗(yàn)對比


c360.gif

dy.gif

如何實(shí)現(xiàn)ShareElement

或許很多人第一次看到類似這種MaterialDesign里炫酷的界面切換效果時止潘,也會有和我一樣的疑惑,
這么炫酷的效果是怎么實(shí)現(xiàn)的辫诅?兩個Activity之間怎么能切換的如此自然凭戴?
實(shí)際上,這樣的效果單憑開發(fā)者自己確實(shí)很難實(shí)現(xiàn)炕矮,幸運(yùn)的是么夫,在Api21之后,官方提供了一套現(xiàn)成的工具來幫我們實(shí)現(xiàn)這個功能肤视,核心就是以下四個函數(shù):

    Window.setEnterTransition()
    Window.setExitTransition()
    Window.setSharedElementEnterTransition()
    Window.setSharedElementExitTransition()

這里我們先以一個簡單的仿官方聯(lián)系人效果的Demo介紹下實(shí)現(xiàn)ShareElement的基本流程

Activity A

public class ContactsActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        /**
         *1档痪、打開FEATURE_CONTENT_TRANSITIONS開關(guān)(可選),這個開關(guān)默認(rèn)是打開的
         */
        requestWindowFeature(Window.FEATURE_CONTENT_TRANSITIONS); 
        /**
         *2邢滑、設(shè)置除ShareElement外其它View的退出方式(左邊滑出)
         */
        getWindow().setExitTransition(new Slide(Gravity.LEFT));
        super.onCreate(savedInstanceState);
        ...
    }
    
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        ...
        /**
         *3腐螟、設(shè)置兩個Activity的共享元素的TransitionName,
         *兩個Activity的共享元素必須設(shè)置同樣的TransitionName
         */
        ViewCompat.setTransitionName(avatarImg,"avatar:"+item.name);
        ViewCompat.setTransitionName(nameTxt,"name:"+item.name);
    }
    
    private void gotoDetailActivity(Contacts contacts, final View avatarImg, final View nameTxt) {
        Intent intent = new Intent(ContactActivity.this,DetailActivity.class);
        Pair<View,String> pair1 = new Pair<>((View)avatarImg,ViewCompat.getTransitionName(avatarImg));
        Pair<View,String> pair2 = new Pair<>((View)nameTxt,ViewCompat.getTransitionName(nameTxt));
        /**
         *4困后、生成帶有共享元素的Bundle乐纸,這樣系統(tǒng)才會知道這幾個元素需要做動畫
         */
        ActivityOptionsCompat activityOptionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(ContactActivity.this, pair1, pair2);
        ActivityCompat.startActivity(ContactActivity.this,intent,activityOptionsCompat.toBundle());
    }
}

Activity B

public class DetailActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);

        ImageView avatarImg = findViewById(R.id.avatar);
        TextView nameTxt = findViewById(R.id.name);
        Contacts item = getIntent().getParcelableExtra(ContactsActivity.KEY_CONTACTS);
        /**
         * 1、設(shè)置相同的TransitionName
         */
        ViewCompat.setTransitionName(avatarImg,"avatar:"+item.name);
        ViewCompat.setTransitionName(nameTxt,"name:"+item.name);
        /**
         * 2摇予、設(shè)置WindowTransition,除指定的ShareElement外汽绢,其它所有View都會執(zhí)行這個Transition動畫
         */
        getWindow().setEnterTransition(new Fade());
        getWindow().setExitTransition(new Fade());
        /**
         * 3、設(shè)置ShareElementTransition,指定的ShareElement會執(zhí)行這個Transiton動畫
         */
        TransitionSet transitionSet = new TransitionSet();
        transitionSet.addTransition(new ChangeBounds());
        transitionSet.addTransition(new ChangeTransform());
        transitionSet.addTarget(avatarImg);
        transitionSet.addTarget(nameTxt);
        getWindow().setSharedElementEnterTransition(transitionSet);
        getWindow().setSharedElementExitTransition(transitionSet);
    }
}

運(yùn)行一下看效果

contacts1.gif

可以看到侧戴,頭像和名字位置是很順利的過渡了宁昭,但是名字的大小和顏色并沒有和之前的官方demo一樣完美過渡,這是因?yàn)楣俜侥J(rèn)提供的Transition動畫只有以下幾個:

ChangeBounds:View的大小與位置動畫
ChangeTransform:View的縮放與旋轉(zhuǎn)動畫
ChangeClipBounds:View的裁剪區(qū)域(View.getClipBounds())動畫
ChangeScroll:處理View的scrollX與scrollY屬性
ChangeImageTransform:處理ImageView的ScaleType屬性(這個在實(shí)際項(xiàng)目中有網(wǎng)絡(luò)圖片時不好用酗宋,后文有解決方案)

可以看到并沒有對TextView的字體大小和顏色做處理

俗話說得好积仗,自己動手豐衣足食,我們來自定義一個Transition動畫

public class ChangeTextTransition extends Transition {
    @Override
    public void captureStartValues(TransitionValues transitionValues) {}

    @Override
    public void captureEndValues(TransitionValues transitionValues) {}

    @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues){
        return super.createAnimator(sceneRoot, startValues, endValues);
    }
}

Transition的設(shè)計(jì)思路是蜕猫,每一個Transition類負(fù)責(zé)整個動畫的一部分斥扛,在這個例子里,TextView的平移和大小變化已經(jīng)由ChangeBounds實(shí)現(xiàn)了,因此我們自定義的Transition只需要實(shí)現(xiàn)字體大小和顏色的動畫就行了

可以看到稀颁,自定義Transition需要實(shí)現(xiàn)三個函數(shù)芬失,要達(dá)到我們想要的效果,需要:
1匾灶、在captureStartValues里獲取到TextView在Activity A里的狀態(tài)(字體和顏色)
2棱烂、在captureEndValues里獲取到TextView在Activity B里的狀態(tài)(字體和顏色)
3、在createAnimator里利用獲取到的初始和結(jié)束狀態(tài)創(chuàng)建一個Animator
最簡單的方法就是在創(chuàng)建ChangeTextTransition的時候傳入相應(yīng)的參數(shù)阶女,不過缺點(diǎn)是:
1颊糜、進(jìn)入和退出時需要不同的參數(shù)
2、如果有多個TextView都需要做動畫怎么辦秃踩?有多少傳多少參數(shù)衬鱼?
3、不夠優(yōu)雅 :)
想要解決以上缺點(diǎn)憔杨,就需要了解ShareElement動畫的完整流程

ShareElement完整流程

要實(shí)現(xiàn)自定義的ShareElement動畫鸟赫,一切的重點(diǎn)都在于Activity對外暴露的回調(diào)SharedElementCallback

SharedElementCallback

你可以通過以下兩個函數(shù)設(shè)置這個回調(diào)

activity.setExitSharedElementCallback(callback)
activity.setEnterSharedElementCallback(callback)

SharedElementCallback有以下7個回調(diào),最麻煩的是消别,這幾個回調(diào)在進(jìn)入和退出時的調(diào)用順序是不一致的

SharedElementCallback是一個抽象類抛蚤,所有回調(diào)都有默認(rèn)實(shí)現(xiàn)

    /**
    *最先調(diào)用,用于動畫開始前替換ShareElements寻狂,比如在Activity B翻過若干頁大圖之后岁经,返回Activity A
    *的時候需要縮小回到對應(yīng)的小圖,就需要在這里進(jìn)行替換
    */
    public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {}

    /**
    *表示ShareElement已經(jīng)全部就位蛇券,可以開始動畫了
    */
    public void onSharedElementsArrived(List<String> sharedElementNames, List<View> sharedElements, OnSharedElementsReadyListener listener) {}

    /**
    *在之前的步驟里(onMapSharedElements)被從ShareElements列表里除掉的View會在此回調(diào)缀壤,
    *不處理的話默認(rèn)進(jìn)行alpha動畫消失
    */
    public void onRejectSharedElements(List<View> rejectedSharedElements) {}
    
    /**
    *在這里會把ShareElement里值得記錄的信息存到為Parcelable格式,以發(fā)送到Activity B
    *默認(rèn)處理規(guī)則是ImageView會特殊記錄Bitmap纠亚、ScaleType诉位、Matrix,其它View只記錄大小和位置
    */
    public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds) {}
    
    /**
    *在這里會把Activity A傳過來的Parcelable數(shù)據(jù)菜枷,重新生成一個View,這個View的大小和位置會與Activity A里的
    *ShareElement一致叁丧,
    */
    public View onCreateSnapshotView(Context context, Parcelable snapshot) {}

    public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {}

    public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {}

下圖展示了從Activity A切換到Activity B啤誊,SharedElementCallback被調(diào)用的時序

ShareElement.png

查看原圖

圖里我標(biāo)了幾個值得注意的點(diǎn):

1、moveSharedElementsToOverlay()

    protected void moveSharedElementsToOverlay() {
        ...
        ViewGroup decor = getDecor();
        if (decor != null) {
            ...
            for (int i = 0; i < numSharedElements; i++) {
                View view = mSharedElements.get(i);
                if (view.isAttachedToWindow()) {
                    ...
                    GhostView.addGhost(view, decor, tempMatrix);
                   ...
                }
            }
        }
    }

ViewOverlay在Android4.3加入拥娄,其父類是ViewGroup,如果想在一個View最上層展示一些東西蚊锹,可以調(diào)用View.getOverlay(),然后調(diào)用ViewOverlay.add(drawable)或者ViewOverlay.getOverlayView().addView()函數(shù)添加到ViewOverlay.

GhostView可以在不改變一個View的Parent的情況下,把View渲染到另一個ViewGroup里面去.

moveSharedElementsToOverlay()函數(shù)實(shí)質(zhì)就是把ShareElementView渲染到整個Activity的最上層(DecorView的ViewOverlay)稚瘾,
這樣在做動畫時ShareElementView就不會被任何別的東西遮擋住.

2牡昆、setSharedElementState()

這里需要提一點(diǎn),在這個Demo里,整個ShareElement動畫過程中丢烘,做動畫的都只有Activity B里的ShareElement,Activity A里的ShareElement唯一的作用就是提供位置大小等參數(shù)柱宦,然后這些參數(shù)在setSharedElementState()函數(shù)里被設(shè)置到Activity B里對應(yīng)的View上.

 private void setSharedElementState(View view, String name, Bundle transitionArgs,
            Matrix tempMatrix, RectF tempRect, int[] decorLoc) {
        ...
        if (view instanceof ImageView) {
            ...
            imageView.setScaleType(scaleType);
            if (scaleType == ImageView.ScaleType.MATRIX) {
                float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
                tempMatrix.setValues(matrixValues);
                imageView.setImageMatrix(tempMatrix);
            }
        }
        ....
        view.setLeft(0);
        view.setTop(0);
        view.setRight(Math.round(width));
        view.setBottom(Math.round(height));
        ...
        view.measure(widthSpec, heightSpec);
        view.layout(x, y, x + width, y + height);
    }

可以看見,如果不是ImageView播瞳,系統(tǒng)只處理了大小位置的信息掸刊,這也是我們前面的動畫里為什么名字的過渡效果那么不自然,因?yàn)橄到y(tǒng)壓根就沒管字體大小和顏色之類的東西.
(如果是進(jìn)入動畫)在設(shè)置好信息之后赢乓,會先調(diào)用SharedElementCallback.onSharedElementStart忧侧,然后就是Transition.captureStartValues()

3、setOriginalSharedElementState()

    protected static void setOriginalSharedElementState(ArrayList<View> sharedElements,
            ArrayList<SharedElementOriginalState> originalState) {
        for (int i = 0; i < originalState.size(); i++) {
            View view = sharedElements.get(i);
            SharedElementOriginalState state = originalState.get(i);
            if (view instanceof ImageView && state.mScaleType != null) {
                ImageView imageView = (ImageView) view;
                imageView.setScaleType(state.mScaleType);
                if (state.mScaleType == ImageView.ScaleType.MATRIX) {
                  imageView.setImageMatrix(state.mMatrix);
                }
            }
            view.setElevation(state.mElevation);
            view.setTranslationZ(state.mTranslationZ);
            int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth,
                    View.MeasureSpec.EXACTLY);
            int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight,
                    View.MeasureSpec.EXACTLY);
            view.measure(widthSpec, heightSpec);
            view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom);
        }
    }

在Transition.captureStartValues()之后牌芋,接著setOriginalSharedElementState()函數(shù)會恢復(fù)view在Activity B里的狀態(tài)蚓炬,
再調(diào)用Transition.captureEndValues().

這時候動畫的起始和結(jié)束狀態(tài)的已經(jīng)獲得了,TransitionManager就會在onPreDraw()的回調(diào)里執(zhí)行Transiton.playTransition(),
這里面會調(diào)用Transition.createAnimator()函數(shù)躺屁,然后執(zhí)行這個Animator.這時候ShareElement動畫就真正開始了.

返回流程

返回流程這里就不詳細(xì)分析了肯夏,直接給出各個回調(diào)的調(diào)用順序

  ActivityB.onMapSharedElements()
->ActivityA.onMapSharedElements()
->ActivityA.onCaptureSharedElementSnapshot()
->ActivityB.onCreateSnapshotView()
->ActivityB.onSharedElementEnd()    
->ActivityB.onSharedElementStart()   //你沒有看錯,就是先End再Start
->ActivityB.onSharedElementsArrived()
->ActivityA.onSharedElementsArrived()
->ActivityA.onRejectSharedElements()
->ActivityA.onCreateSnapshotView()
->ActivityA.onSharedElementStart()
->ActivityA.onSharedElementEnd()

自定義Transition

由上面的分析可以得出楼咳,要實(shí)現(xiàn)TextView的Transition熄捍,需要以下步驟

EnterTransition.png

查看原圖

實(shí)際代碼可參考ChangeTextTransition

YcShareElement

demo里用了
GSYVideoPlayer展示視頻
FrescoGlide展示圖片

YcShareElement提供了兩個demo母怜,一個是上面的聯(lián)系人demo余耽,另一個實(shí)現(xiàn)了圖片、視頻混合的列表頁與詳情頁之間的ShareElement動畫苹熏,如下圖

YcShareElementDemo

這里面的關(guān)鍵點(diǎn)如下:
1碟贾、Glide圖片的ShareElement動畫
ImageView在動畫過程中要經(jīng)歷默認(rèn)背景色->小縮略圖->大圖三個階段,如何在這三個階段里做到無縫切換
參考:ChangeOnlineImageTransition
2轨域、Fresco圖片的ShareElement動畫
Fresco提供了內(nèi)置的DraweeTransition袱耽,但是如果設(shè)置了縮略圖,圖片就會變形干发,并且必須在構(gòu)造函數(shù)里提供動畫起始的ScaleType信息朱巨,簡單的情況很好用,在復(fù)雜的情況下不太友好
參考:AdvancedDraweeTransition
3枉长、從列表的Webp動圖到詳情頁的視頻ShareElement動畫
這個在實(shí)現(xiàn)了以上兩點(diǎn)之后其實(shí)就很簡單了,實(shí)際上就是視頻的封面圖做動畫

普通頁面使用步驟

1冀续、打開WindowContentTransition開關(guān)

YcShareElement.enableContentTransition(getApplication());  

由于這個開關(guān)默認(rèn)是打開的,因此這一句是可選的必峰,擔(dān)心遇到奇葩手機(jī)關(guān)掉這個開關(guān)的可以調(diào)用

2洪唐、生成Bundle,然后startActivity

    private void gotoDetailActivity(){
        Intent intent = new Intent(this, DetailActivity.class);
        Bundle bundle = YcShareElement.buildOptionsBundle(ContactActivity.this, new IShareElements() {
            @Override
            public ShareElementInfo[] getShareElements() {
                return new ShareElementInfo[]{new ShareElementInfo(mAvatarImg),
                        new ShareElementInfo(mNameTxt, new TextViewStateSaver())};
            }
        });
        ActivityCompat.startActivity(ContactActivity.this, intent, bundle);
    }

3吼蚁、新的頁面里設(shè)置并啟動Transition

public class DetailActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        YcShareElement.setEnterTransition(this, new IShareElements() {
            @Override
            public ShareElementInfo[] getShareElements() {
                return new ShareElementInfo[]{new ShareElementInfo(avatarImg),
                        new ShareElementInfo(nameTxt, new TextViewStateSaver())};
            }
        });
        YcShareElement.startTransition(this);
    }
}

YcShareElement.setEnterTransition()默認(rèn)會暫停Activity的Transtion動畫凭需,直到調(diào)用YcShareElement.startTransition(),
在這種不需要等待ShareElement加載的簡單頁面,可以將第三個參數(shù)傳false,就不會暫停ActivityB的Transition動畫了,如下

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        YcShareElement.setEnterTransition(this, new IShareElements() {
            @Override
            public ShareElementInfo[] getShareElements() {
                return new ShareElementInfo[]{new ShareElementInfo(avatarImg),
                        new ShareElementInfo(nameTxt, new TextViewStateSaver())};
            }
        },false);
    }

效果如下:


contacts2.gif

圖片&視頻頁面使用步驟

1粒蜈、打開WindowContentTransition開關(guān)

    YcShareElement.enableContentTransition(getApplication());  

2顺献、生成Bundle,然后startActivity

    Bundle options = YcShareElement.buildOptionsBundle(getActivity(), this);
    startActivityForResult(intent, REQUEST_CONTENT, options);

3薪伏、Activity B設(shè)置Transtion動畫

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        YcShareElement.setEnterTransition(this, this);
        ...
    }

4滚澜、Activity B的ViewPager加載好之后啟動Transition

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        ...加載數(shù)據(jù)...
        YcShareElement.postStartTransition(getActivity());
    }

這時候進(jìn)入動畫就執(zhí)行完畢了,接下來要處理滑動若干頁之后返回列表頁的情況

5嫁怀、Activity B實(shí)現(xiàn)finishAfterTransition()函數(shù)

    @Override
    public void finishAfterTransition() {
        YcShareElement.finishAfterTransition(this, this);
        super.finishAfterTransition();
    }

6设捐、Activity A實(shí)現(xiàn)onActivityReenter()函數(shù)

    @Override
    public void onActivityReenter(int resultCode, Intent data) {
        super.onActivityReenter(resultCode, data);
        YcShareElement.onActivityReenter(this, resultCode, data, new IShareElementSelector() {
            @Override
            public void selectShareElements(List<ShareElementInfo> list) {
                //將列表頁滑動到變更后的ShareElement的位置
                mFragment.selectShareElement(list.get(0));
            }
        });
    }

如何擴(kuò)展支持自定義View的Transition動畫

這里以Fresco為例介紹如何進(jìn)行擴(kuò)展

1、確定所需參數(shù)

首先確定SimpleDraweeView做Transtion動畫需要的參數(shù)塘淑,即ActualImageScaleType

2萝招、繼承ViewStateSaver,獲取所需參數(shù)

public class FrescoViewStateSaver extends ViewStateSaver {

    @Override
    protected void captureViewInfo(View view, Bundle bundle) {
        if (view instanceof GenericDraweeView) {
            int actualScaleTypeInt = scaleTypeToInt(((GenericDraweeView)view).getHierarchy().getActualImageScaleType())
            bundle.putInt("scaleType",actualScaleTypeInt);
        }
    }
    
    public ScalingUtils.ScaleType getScaleType(Bundle bundle) {
        int scaleType = bundle.getInt("scaleType", 0);
        return intToScaleType(scaleType);
    }
}

3存捺、自定義Transition

public class AdvancedDraweeTransition extends Transition {
    private ScalingUtils.ScaleType mFromScale;
    private ScalingUtils.ScaleType mToScale;

    public AdvancedDraweeTransition() {
        addTarget(GenericDraweeView.class);
    }

    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        ...
        ShareElementInfo shareElementInfo = ShareElementInfo.getFromView(transitionValues.view);
        mFromScale = ((FrescoViewStateSaver) shareElementInfo.getViewStateSaver()).getScaleType(viewInfo);
        ...
    }

    @Override
    public void captureEndValues(TransitionValues transitionValues) {
        ...
        ShareElementInfo shareElementInfo = ShareElementInfo.getFromView(transitionValues.view);
        mToScale = ((FrescoViewStateSaver) shareElementInfo.getViewStateSaver()).getScaleType(viewInfo);
        ...
    }

    @Override
    public Animator createAnimator(
            ViewGroup sceneRoot,
            TransitionValues startValues,
            TransitionValues endValues) {
        ..
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float fraction = (float) animation.getAnimatedValue();
                scaleType.setValue(fraction);
                if (draweeView.getHierarchy().getActualImageScaleType() != scaleType) {
                    draweeView.getHierarchy().setActualImageScaleType(scaleType);
                }
            }
        });
        ...
        return animator;
    }
}

4槐沼、使用自定義的Transition

public class FrescoShareElementTransitionfactory extends DefaultShareElementTransitionFactory {
    @Override
    protected TransitionSet buildShareElementsTransition(List<View> shareViewList) {
        TransitionSet transitionSet =  super.buildShareElementsTransition(shareViewList);
        transitionSet.addTransition(new AdvancedDraweeTransition());
        return transitionSet;
    }
}
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        YcShareElement.setEnterTransitions(this, this,true,new FrescoShareElementTransitionfactory());
        ...
    }

廣告時間

在文末安利一下我的另外幾個開源庫,歡迎大家來提issue捌治、star岗钩、fork

PhotoMovie:高仿抖音照片電影功能
VideoProcessor:用硬編碼實(shí)現(xiàn)視頻的快慢放、倒流及混音功能
SVideoRecorder:硬編碼短視頻錄制肖油,支持分段錄制兼吓、所見即所得

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市森枪,隨后出現(xiàn)的幾起案子视搏,更是在濱河造成了極大的恐慌,老刑警劉巖县袱,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浑娜,死亡現(xiàn)場離奇詭異,居然都是意外死亡式散,警方通過查閱死者的電腦和手機(jī)筋遭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來暴拄,“玉大人漓滔,你說我怎么就攤上這事∽嵋疲” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵反肋,是天一觀的道長那伐。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么罕邀? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任畅形,我火速辦了婚禮,結(jié)果婚禮上诉探,老公的妹妹穿的比我還像新娘日熬。我一直安慰自己,他們只是感情好肾胯,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布竖席。 她就那樣靜靜地躺著,像睡著了一般敬肚。 火紅的嫁衣襯著肌膚如雪毕荐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天艳馒,我揣著相機(jī)與錄音憎亚,去河邊找鬼。 笑死弄慰,一個胖子當(dāng)著我的面吹牛第美,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播陆爽,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼什往,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了墓陈?” 一聲冷哼從身側(cè)響起恶守,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贡必,沒想到半個月后兔港,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仔拟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年衫樊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片利花。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡科侈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炒事,到底是詐尸還是另有隱情臀栈,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布挠乳,位于F島的核電站权薯,受9級特大地震影響姑躲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盟蚣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一黍析、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屎开,春花似錦阐枣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至如孝,卻和暖如春宪哩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背第晰。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工锁孟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茁瘦。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓品抽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親甜熔。 傳聞我的和親對象是個殘疾皇子圆恤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,510評論 25 707
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料腔稀? 從這篇文章中你...
    hw1212閱讀 12,693評論 2 59
  • 【Android 動畫】 動畫分類補(bǔ)間動畫(Tween動畫)幀動畫(Frame 動畫)屬性動畫(Property ...
    Rtia閱讀 6,103評論 1 38
  • 1盆昙、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_x閱讀 15,968評論 3 119
  • 經(jīng)常都有這樣子感覺:被突如其來的危機(jī)感诵闭,給恐懼了炼团!到了某個年齡階層,還一無所獲疏尿,的確很悲哀瘟芝。或許來自于對比的壓力褥琐,...
    念叨嘮閱讀 248評論 0 0