1 /* 2 * Copyright 2021 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 package org.skia.jetski.util; 9 10 import android.content.Context; 11 12 import android.content.res.TypedArray; 13 import android.util.AttributeSet; 14 import android.view.SurfaceView; 15 16 import android.view.ViewGroup; 17 import android.widget.FrameLayout; 18 import java.io.InputStream; 19 20 import org.skia.jetski.Canvas; 21 import org.skia.jetski.Color; 22 import org.skia.jetski.Matrix; 23 import org.skia.jetski.SkottieAnimation; 24 import org.skia.jetski.Surface; 25 26 import org.skia.jetski.R; 27 28 class SkottieRenderer extends SurfaceRenderer { 29 private float mSurfaceWidth, 30 mSurfaceHeight; 31 private Color mBackground; 32 private SkottieAnimation mAnimation; 33 private boolean mPlaying; 34 SkottieRenderer(SkottieAnimation mAnimation, Color mBackground)35 SkottieRenderer(SkottieAnimation mAnimation, Color mBackground) { 36 this.mAnimation = mAnimation; 37 this.mBackground = mBackground; 38 } 39 @Override onSurfaceInitialized(Surface surface)40 protected void onSurfaceInitialized(Surface surface) { 41 mSurfaceWidth = surface.getWidth(); 42 mSurfaceHeight = surface.getHeight(); 43 mPlaying = true; 44 } 45 46 @Override onRenderFrame(Canvas canvas, long ms)47 protected void onRenderFrame(Canvas canvas, long ms) { 48 if(mPlaying) { 49 canvas.drawColor(mBackground); 50 double t = (double)ms / 1000 % mAnimation.getDuration(); 51 mAnimation.seekTime(t); 52 53 float s = Math.min(mSurfaceWidth / mAnimation.getWidth(), 54 mSurfaceHeight / mAnimation.getHeight()); 55 canvas.save(); 56 canvas.concat(new Matrix().translate((mSurfaceWidth - s*mAnimation.getWidth())/2, 57 (mSurfaceHeight - s*mAnimation.getHeight())/2) 58 .scale(s, s)); 59 60 mAnimation.render(canvas); 61 canvas.restore(); 62 } 63 } 64 play()65 void play() { 66 if (!mPlaying) { 67 mPlaying = true; 68 } 69 } 70 pause()71 void pause() { 72 if (mPlaying) { 73 mPlaying = false; 74 } 75 } 76 getAnimation()77 SkottieAnimation getAnimation() { 78 return mAnimation; 79 } 80 81 @Override release()82 public void release() { 83 mAnimation.release(); 84 super.release(); 85 } 86 87 @Override finalize()88 protected void finalize() throws Throwable { 89 this.release(); 90 } 91 } 92 93 public class SkottieView extends FrameLayout { 94 private SurfaceView mBackingView; 95 private SkottieRenderer mRenderer; 96 97 private final String LOG_TAG = "SkottieView"; 98 SkottieView(Context context, int resID, Color background)99 public SkottieView(Context context, int resID, Color background) { 100 super(context); 101 mBackingView = new SurfaceView(context); 102 initBackingView(); 103 InputStream inputStream = context.getResources().openRawResource(resID); 104 mRenderer = new SkottieRenderer(makeAnimation(inputStream), background); 105 mBackingView.getHolder().addCallback(mRenderer); 106 } 107 SkottieView(Context context)108 public SkottieView(Context context) { 109 super(context); 110 mBackingView = new SurfaceView(context); 111 initBackingView(); 112 } 113 SkottieView(Context context, AttributeSet attrs)114 public SkottieView(Context context, AttributeSet attrs) { 115 this(context, attrs, 0, 0); 116 } 117 SkottieView(Context context, AttributeSet attrs, int defStyleAttr)118 public SkottieView(Context context, AttributeSet attrs, int defStyleAttr) { 119 this(context, attrs, defStyleAttr, 0); 120 } 121 122 // SkottieView constructor when initialized in XML layout SkottieView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)123 public SkottieView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 124 super(context, attrs); 125 TypedArray a = context.getTheme() 126 .obtainStyledAttributes(attrs, R.styleable.SkottieView, defStyleAttr, defStyleRes); 127 try { 128 // set backing view and background color 129 mBackingView = new SurfaceView(context); 130 initBackingView(); 131 int backgroundColor = a.getColor(R.styleable.SkottieView_background_color, -1); 132 if (backgroundColor == -1) { 133 throw new RuntimeException("background_color attribute " 134 + "needed for SurfaceView"); 135 } 136 if (android.graphics.Color.alpha(backgroundColor) != 255) { 137 throw new RuntimeException("background_color cannot be transparent"); 138 } 139 // set source 140 int src = a.getResourceId(R.styleable.SkottieView_src, -1); 141 Color c = new Color(backgroundColor); 142 setSource(src, context, new Color(backgroundColor)); 143 } finally { 144 a.recycle(); 145 } 146 } 147 initBackingView()148 private void initBackingView() { 149 mBackingView.setLayoutParams(new ViewGroup.LayoutParams( 150 ViewGroup.LayoutParams.MATCH_PARENT, 151 ViewGroup.LayoutParams.MATCH_PARENT)); 152 addView(mBackingView); 153 } 154 makeAnimation(InputStream is)155 static private SkottieAnimation makeAnimation(InputStream is) { 156 String json = ""; 157 try { 158 byte[] data = new byte[is.available()]; 159 is.read(data); 160 json = new String(data); 161 } catch (Exception e) {} 162 return new SkottieAnimation(json); 163 } 164 setSource(int resID, Context context, Color background)165 public void setSource(int resID, Context context, Color background) { 166 InputStream inputStream = context.getResources().openRawResource(resID); 167 mRenderer = new SkottieRenderer(makeAnimation(inputStream), background); 168 mBackingView.getHolder().addCallback(mRenderer); 169 } 170 play()171 public void play() { 172 mRenderer.play(); 173 } 174 pause()175 public void pause() { 176 mRenderer.pause(); 177 } 178 seekTime(double t)179 public void seekTime(double t) { 180 mRenderer.setBaseTime(java.lang.System.currentTimeMillis() - ((long)t * 1000)); 181 } 182 seekFrame(double f)183 public void seekFrame(double f) { 184 double totalFrames = mRenderer.getAnimation().getFrameCount(); 185 double totalTime = mRenderer.getAnimation().getDuration(); 186 double targetTime = (f % totalFrames) / totalFrames * totalTime; 187 seekTime(targetTime); 188 } 189 } 190