欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android實現(xiàn)同頻共幀動畫效果

 更新時間:2024年01月22日 09:42:30   作者:時光少年  
我們聽過“同頻共振”,其原理是多個物體物體以同樣的頻率振動,但是本篇實現(xiàn)的效果是“同頻共幀”,含義是:動畫以同樣的頻率和同樣的幀展示在多個不同View上,文中通過代碼示例介紹的非常詳細,感興趣的同學可以自己動手嘗試一下

同頻共幀

我們聽過“同頻共振”,其原理是多個物體物體以同樣的頻率振動,但是本篇實現(xiàn)的效果是“同頻共幀”,含義是:動畫以同樣的頻率和同樣的幀展示在多個不同View上。

特點:

  • 動畫效果
  • 同樣的頻率
  • 同樣的幀 (嚴格意義上是小于1個vsync信號的幀)
  • 多個不同View同時展示

之前的文章中我們實現(xiàn)了很多動效,但幾乎都是基于View本身實現(xiàn)的,但是在Android中,Drawable最容易擴展的動效工具,通過Drawable提供的接口,我們可以接入libpag、lottie、SVG、APNG、gif,LazyAnimationDrawable、AnimationDrawable等動效,更加方便移植,同時Drawable支持setHotspot和setState接口,可以實現(xiàn)復雜度較低的交互效果。

這種動效其實在手機版QQ上就有,如果你給自己的頭像設置為一個動態(tài)圖,那么在群聊連發(fā)多條消息,那么你就會發(fā)現(xiàn),在同一個頁面上你的頭像動畫是同步展示的。

現(xiàn)狀 & 痛點

現(xiàn)狀

我們以幀動畫問題展開,要知道幀動畫有難以容忍的內存占用問題、以及主線程解碼問題,同時包體積問題也相當嚴重,為此市面上出現(xiàn)了很多方案。libpag、lottie、VapPlayer、AlphaPlayer、APNG、GIF、SVGA、AnimationDrawable等。但你在開發(fā)時就會發(fā)現(xiàn),每一種引擎都有自己獨特的優(yōu)勢,也有自己獨特的劣勢,你往往想著用一種引擎統(tǒng)一所有動效實現(xiàn),但往往現(xiàn)實不允許。

我們來說說幾大引擎的優(yōu)缺點:

libPag: 目前支持功能最多的動效引擎,普通動畫性能也非常不錯,相比其他引擎快很多。該引擎使用自研渲染引擎和解碼器實現(xiàn),但是對于預合成動效(超長動效和復雜動效可能會用到),由于其使用的是軟解,在低配設備上比VapPlayer和AlphaPlayer卡的多,另外lib so相比其他引擎也是大很多。

VapPlayer/AlphaPlayer : 這兩種其都是通過alpha 遮罩實現(xiàn),大部分情況下使用的是設備硬解碼器,不過,VapPlayer缺乏硬解碼器篩選機制,偶爾有時會拿到軟解碼器,另外其本身存在音畫同步問題,至于AlphaPlayer把播放交給系統(tǒng)和三方播放器,避免了此類問題。但是,如果是音視頻類app,他們都有共同的問題,終端設備上硬解碼器的實例數(shù)量是受限制的,甚至有的設備解碼器同一時刻只能使用一個,使用這兩種解碼器就會造成業(yè)務播放器綠屏、起播失敗、解碼器卡住等問題。不過解決辦法是將特效和業(yè)務播放器資源類型隔離,如果業(yè)務播放器是使用h264,那么你可以動效使用h264\mpeg2等其他編碼類型。

lottie: lottie目前是比較廣為人知的動效引擎,使用也相當廣泛。但其存在跨平臺兼容性,缺少很多特效,其性能是不如libpag的,不過總體能覆蓋到大部分場景。另一個開發(fā)中常常會遇到的問題是,UI設計人員對于lottie的compose layer理解存在問題,往往會出現(xiàn)將lottie動畫做成和幀動畫一樣的動畫,顯然,compose layer的思想是多張圖片合成,那就意味著圖片本身應該有大有小,按一定軌跡運動和漸變,而不是一幀一幀簡單播放。

APNG、GIF : 這類動畫屬于資源型動畫,其本身存在很多缺點,比如占內存和耗cpu,不過簡單的場景還是可以使用的。

SVGA:很多平臺對這種動畫抱有期待,特別是其矢量性質和低內存的特點,然而,其本身面臨標準不統(tǒng)一的問題,造成跨平臺的能力不足。

LazyAnimationDrawable:幾乎所有的動畫對低配設備都不友好,幀動畫比上不足比下有余,低配設備上,為了解決libpag、VapPlayer、lottie對低配設備上音視頻類app不友好的問題,使用AnimationDrawble顯然是不行的,因此我們往往會實現(xiàn)了自己的AnimationDrawable,使其具備兜底的能力: 異步解碼 + 展示一幀預加載下一幀的能力,其實也就是LazyAnimationDrawable。

痛點

以上我們羅列了很多問題,看似和我們的主要目的毫無關系,其實我們可以想想,如果使用上述引擎,哪種方式可以實現(xiàn)兼容性更好的“同頻共幀”動效呢 ?

實際上,幾乎沒有引擎能承擔此任務,那有沒有辦法實現(xiàn)呢?

原理

我們很難讓每個View同時執(zhí)行和繪制同樣的畫面,另一個問題是,如果設計多個View繪制Bitmap,那么還可能造成資源加載的內存OOM的問題。另外一方面如果使用LazyAnimationDrawable、VapX、AlphaPlayer等 ,如果同時執(zhí)行,那么解碼線程需要創(chuàng)建多個,顯然性能問題也是重中之重。

有沒有更加簡單方法呢 ?

實際上是有的,那就是投影。

我們無論使用CompositeDrawable、LazyAnimationDrawable、AnimationDrawable還是VectorDrawable,我們可以保證在使用個實例的情況下,將畫面繪制到不同View上即可。

不過:本篇以AnimationDrawable 為例子實現(xiàn),其實其他Drawable動畫類似。

實現(xiàn)

但是這種難度也是很高的,如果我們使用一個View 管理器,然后構建一個播放器,顯然還要處理View各種狀態(tài),顯然避免不了耦合問題。這里我們回到開頭說過的drawable方案,當然,一個drawable顯然無法設置給多個View,這點顯然是我們需要處理的難點,此外,每個View的大小也不一致,如何處理這種問題呢。

Drawable加殼

我們參考Glide中com.bumptech.glide.request.target.FixedSizeDrawable 實現(xiàn),其原理是通過FixedSizeDrawable代理真實的drawble繪制,然后利用Matrix實現(xiàn)Canvas縮放,即可適配不同大小的View。

FixedSizeDrawable(State state, Drawable wrapped) {
  this.state = Preconditions.checkNotNull(state);
  this.wrapped = Preconditions.checkNotNull(wrapped);

  // We will do our own scaling.
  wrapped.setBounds(0, 0, wrapped.getIntrinsicWidth(), wrapped.getIntrinsicHeight());

  matrix = new Matrix();
  wrappedRect = new RectF(0, 0, wrapped.getIntrinsicWidth(), wrapped.getIntrinsicHeight());
  bounds = new RectF();
}

Matrix 的作用

matrix.setRectToRect(wrappedRect, drawableBounds, Matrix.ScaleToFit.CENTER);
canvas.concat(matrix);  //Canvas Matrix 轉換

當然,必要時支持下alpha和colorFilter,下面是完整實現(xiàn)。

public static class AnimationDrawableWrapper extends Drawable {

    private final AnimationDrawable animationDrawable; //動畫drawable
    private final Matrix matrix = new Matrix();
    private final RectF wrappedRect;
    private final RectF drawableBounds;
    private int alpha = 255;
    private ColorFilter colorFilter;

    public AnimationDrawableWrapper(AnimationDrawable drawable) {
        this.animationDrawable = drawable;
        this.wrappedRect = new RectF(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        this.drawableBounds = new RectF();
    }

    @Override
    public void draw(Canvas canvas) {
        Drawable current = animationDrawable.getCurrent();
        if (current == null) {
            return;
        }
        current.setAlpha(this.alpha);
        current.setColorFilter(colorFilter);
        Rect drawableRect = current.getBounds();
        wrappedRect.set(drawableRect);
        drawableBounds.set(getBounds());
       
       // 變化坐標
        matrix.setRectToRect(wrappedRect, drawableBounds, Matrix.ScaleToFit.CENTER);
        
        int save = canvas.save();
        
        canvas.concat(matrix);
        current.draw(canvas);
        
        canvas.restoreToCount(save);
        
        current.setAlpha(255);//還原
        current.setColorFilter(null); //還原
     

    }

    @Override
    public void setAlpha(int alpha) {
        this.alpha = alpha;
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        this.colorFilter = colorFilter;
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

}

View更新

我們知道AnimationDrawable每一幀都是不一樣的,那怎么將每一幀都能繪制在View上呢,了解過Drawable更新機制的開發(fā)者都知道,每一個View都實現(xiàn)了Drawable.Callback,當給View設置drawable時,Drawable.Callback也會設置給drawable。

Drawable刷新View時需要調用invalidate,顯然是通過Drawable.Callback實現(xiàn),當然,Drawable自身就實現(xiàn)了更新方法Drawable#invalidateSelf,我們只需要調用改方法刷新View即可觸發(fā)View#onDraw,從而觸發(fā)drawable#draw方法。

public void invalidateSelf() {
    final Callback callback = getCallback();
    if (callback != null) {
        callback.invalidateDrawable(this);
    }
}

更新AnimationDrawable

顯然,任何動畫都具備時間屬性,因此更新Drawable是必要的,View本身是可以通過Drawable.Callback機制更新Drawable的。通過scheduleDrawable和unscheduleDrawable 定時處理Runnable和取消Runnable。

public interface Callback {

    void invalidateDrawable(@NonNull Drawable who);
  
    void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);

    void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
}

而AnimationDrawable實現(xiàn)了Runnable接口

@Override
public void run() {
    nextFrame(false);
}

然而,如果使用的RecyclerView,那么還可能會出現(xiàn)View 從頁面移除的問題,因此依靠View顯然是不行的,這里我們引入Handler或者Choreograper。

this.choreographer = Choreographer.getInstance();

但是,我們什么時候調用呢?顯然還得利用Drawable.Callback機制

給animationDrawable設置Drawable.Callback

this.drawable.setCallback(callback);

更新邏輯實現(xiàn)

@Override
public void invalidateDrawable(@NonNull Drawable who) {
//更新所有wrapper
    for (int i = 0; i < drawableList.size(); i++) {
        WeakReference<AnimationDrawableWrapper> reference = drawableList.get(i);
        AnimationDrawableWrapper wrapper = reference.get();
        if (wrapper == null) {
            return;
        }
        wrapper.invalidateSelf();
    }
}

@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
    this.scheduleTask = what;
    this.choreographer.postFrameCallbackDelayed(this, when - SystemClock.uptimeMillis());
}

@Override
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
    this.scheduleTask = null;
    this.choreographer.removeFrameCallback(this);
}

既然使用Choreographer,那doFrame需要實現(xiàn)的

@Override
public void doFrame(long frameTimeNanos) {
    if(this.scheduleTask != null) {
        this.scheduleTask.run();
    }
}

好了,以上就是核心邏輯,到此我們就實現(xiàn)了核心邏輯

完整代碼

public class MirrorFrameAnimation implements Drawable.Callback, Choreographer.FrameCallback {
    private final Drawable drawable;
    private final int drawableWidth;
    private final int drawableHeight;
    private List<WeakReference<AnimationDrawableWrapper>> drawableList = new ArrayList<>();
    private Choreographer choreographer;
    private Runnable scheduleTask;

    public MirrorFrameAnimation(Resources resources, int resId, int drawableWidth, int drawableHeight) {
      
        //設置寬高,防止AnimationDrawable大小不穩(wěn)定問題
        this.drawableWidth = drawableWidth;
        this.drawableHeight = drawableHeight;
        this.drawable = resources.getDrawable(resId);
        this.drawable.setBounds(0, 0, drawableHeight, drawableHeight);
        this.drawable.setCallback(this);
        this.choreographer = Choreographer.getInstance();
    }

    public void start() {
        choreographer.removeFrameCallback(this);
        if (drawable instanceof AnimationDrawable) {
            ((AnimationDrawable) drawable).start();
        }
    }

    public void stop() {
        choreographer.removeFrameCallback(this);
        if (drawable instanceof AnimationDrawable) {
            ((AnimationDrawable) drawable).stop();
        }
    }

    /**
     * @return The number of frames in the animation
     */
    public int getNumberOfFrames() {
        if (drawable instanceof AnimationDrawable) {
           return  ((AnimationDrawable) drawable).getNumberOfFrames();
        }
        return 1;
    }

    /**
     * @return The Drawable at the specified frame index
     */
    public Drawable getFrame(int index) {
        if (drawable instanceof AnimationDrawable) {
            return  ((AnimationDrawable) drawable).getFrame(index);
        }
        return drawable;
    }

    /**
     * @return The duration in milliseconds of the frame at the
     *         specified index
     */
    public int getDuration(int index) {
        if (drawable instanceof AnimationDrawable) {
            return  ((AnimationDrawable) drawable).getDuration(index);
        }
        return -1;
    }

    /**
     * @return True of the animation will play once, false otherwise
     */
    public boolean isOneShot() {
        if (drawable instanceof AnimationDrawable) {
            return  ((AnimationDrawable) drawable).isOneShot();
        }
        return true;
    }

    /**
     * Sets whether the animation should play once or repeat.
     *
     * @param oneShot Pass true if the animation should only play once
     */
    public void setOneShot(boolean oneShot) {
        if (drawable instanceof AnimationDrawable) {
              ((AnimationDrawable) drawable).setOneShot(oneShot);
        }
    }

    public void syncDrawable(View view) {
        if (!(drawable instanceof AnimationDrawable)) {
            if(view instanceof ImageView) {
                ((ImageView) view).setImageDrawable(drawable);
            }else{
                view.setBackground(drawable);
            }
            return;
        }

        AnimationDrawableWrapper wrapper = new AnimationDrawableWrapper((AnimationDrawable) drawable);
        drawableList.add(new WeakReference<>(wrapper));

        if(view instanceof ImageView) {
            ((ImageView) view).setImageDrawable(wrapper);
        }else{
            view.setBackground(wrapper);
        }
    }

    @Override
    public void invalidateDrawable(@NonNull Drawable who) {
        for (int i = 0; i < drawableList.size(); i++) {
            WeakReference<AnimationDrawableWrapper> reference = drawableList.get(i);
            AnimationDrawableWrapper wrapper = reference.get();
            if (wrapper == null) {
                return;
            }
            wrapper.invalidateSelf();
        }
    }

    @Override
    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
        this.scheduleTask = what;
        this.choreographer.postFrameCallbackDelayed(this, when - SystemClock.uptimeMillis());
    }

    @Override
    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
        this.scheduleTask = null;
        this.choreographer.removeFrameCallback(this);
    }


    @Override
    public void doFrame(long frameTimeNanos) {
        if(this.scheduleTask != null) {
            this.scheduleTask.run();
        }
    }

}

使用方法

int dp2px = (int) dp2px(100);
MirrorFrameAnimation mirrorFrameAnimation = new MirrorFrameAnimation(getResources(),R.drawable.loading_animation,dp2px,dp2px);

mirrorFrameAnimation.syncDrawable(imageView1);
mirrorFrameAnimation.syncDrawable(imageView2);
mirrorFrameAnimation.syncDrawable(imageView3);
mirrorFrameAnimation.syncDrawable(imageView4);
mirrorFrameAnimation.syncDrawable(imageView5);
mirrorFrameAnimation.syncDrawable(imageView6);

mStart.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mirrorFrameAnimation.start();
    }
});
mStop.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mirrorFrameAnimation.stop();
    }
});

適用范圍

圖像同步執(zhí)行需求

本篇我們實現(xiàn)了“同頻共幀動效”,實際上這也是一種對稱動畫的優(yōu)化方法。

我們常常會出現(xiàn)屏幕邊緣方向同時展示相同動畫的問題,由于每個動畫啟動存在一定的延時,以及控制邏輯不穩(wěn)定,往往會出現(xiàn)一邊動畫播放結束,另一邊動畫還在展示的情況。

總結

動效一直是Android設備的上需要花大力氣優(yōu)化的,如果是圖像同步執(zhí)行、對稱動效,本篇方案顯然可以幫助我們減少線程和內存的消耗。

以上就是Android實現(xiàn)同頻共幀動畫效果的詳細內容,更多關于Android同頻共幀動畫的資料請關注腳本之家其它相關文章!

相關文章

  • 淺析Android中getWidth()和getMeasuredWidth()的區(qū)別

    淺析Android中getWidth()和getMeasuredWidth()的區(qū)別

    這篇文章主要介紹了淺析Android中getWidth()和getMeasuredWidth()的區(qū)別 ,getMeasuredWidth()獲取的是view原始的大小,getWidth()獲取的是這個view最終顯示的大小,具體區(qū)別介紹大家參考下本文
    2018-04-04
  • Android布局實現(xiàn)圓角邊框效果

    Android布局實現(xiàn)圓角邊框效果

    這篇文章主要介紹了Android布局實現(xiàn)圓角邊框效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • android中打開相機、打開相冊進行圖片的獲取示例

    android中打開相機、打開相冊進行圖片的獲取示例

    本篇文章主要介紹了android中打開相機、打開相冊進行圖片的獲取示例,非常具有實用價值,需要的朋友可以參考下。
    2017-01-01
  • Android判斷服務是否運行及定位問題實例分析

    Android判斷服務是否運行及定位問題實例分析

    這篇文章主要介紹了Android判斷服務是否運行及定位問題,以實例形式較為詳細的分析了Android判斷服務運行狀態(tài)及獲取經緯度的相關實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-09-09
  • Android.mk文件使用速通手冊

    Android.mk文件使用速通手冊

    本文將簡要介紹 Android.mk 的使用,看完本文,你將學會如何將自己的代碼通過 Android.mk 來編譯到工程中,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • 在Visual Studio上構建C++的GUI框架wxWidgets的開發(fā)環(huán)境

    在Visual Studio上構建C++的GUI框架wxWidgets的開發(fā)環(huán)境

    這篇文章主要介紹了Visual Studio上構件C++的GUI框架wxWidgets開發(fā)環(huán)境的方法,wxWidgets是一個跨多個系統(tǒng)平臺的圖形化界面開發(fā)框架,并且可用語言不限于C++,需要的朋友可以參考下
    2016-04-04
  • Android實現(xiàn)自定義飄雪效果

    Android實現(xiàn)自定義飄雪效果

    隨著冬季的腳步越來越遠,南方的我今年就看了一場雪,下一場雪遙遙無期,那我們來實現(xiàn)一個自定義的 View,它能模擬雪花飄落的景象,所以本文給大家介紹了基于Android實現(xiàn)自定義飄雪效果,感興趣的朋友可以參考下
    2024-01-01
  • Android實現(xiàn)將View保存成Bitmap的方法

    Android實現(xiàn)將View保存成Bitmap的方法

    這篇文章主要介紹了Android實現(xiàn)將View保存成Bitmap的方法,涉及Android畫布Canvas、位圖bitmap及View的相關使用技巧,需要的朋友可以參考下
    2016-06-06
  • Android自定義View實現(xiàn)漸變色進度條

    Android自定義View實現(xiàn)漸變色進度條

    這篇文章主要為大家詳細介紹了Android自定義View實現(xiàn)漸變色進度條,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • Android開發(fā) OpenGL ES繪制3D 圖形實例詳解

    Android開發(fā) OpenGL ES繪制3D 圖形實例詳解

    這篇文章主要介紹了Android開發(fā) OpenGL ES繪制3D 圖形實例詳解的相關資料,需要的朋友可以參考下
    2016-09-09

最新評論