Add SkinAndAnimationBounds and use in Play/Pause Sample

This commit is contained in:
Denis Andrasec 2024-07-04 11:32:49 +02:00
parent 092d97ec13
commit 2c8dd49a85
5 changed files with 137 additions and 19 deletions

View File

@ -18,6 +18,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView
import com.esotericsoftware.spine.android.bounds.SkinAndAnimationBounds
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -58,16 +59,18 @@ fun PlayPause(
AndroidView(
factory = { ctx ->
SpineView(ctx).apply {
loadFromAsset(
"dragon.atlas",
"dragon-ess.skel",
controller
)
}
SpineView.Builder(ctx)
.setBoundsProvider(SkinAndAnimationBounds("flying"))
.build()
.apply {
loadFromAsset(
"dragon.atlas",
"dragon-ess.skel",
controller
)
}
},
modifier = Modifier.padding(paddingValues)
)
}
}
}

View File

@ -50,6 +50,35 @@ import java.io.File;
import java.net.URL;
public class SpineView extends View implements Choreographer.FrameCallback {
public static class Builder {
private final Context context;
private BoundsProvider boundsProvider = new SetupPoseBounds();
private Alignment alignment = Alignment.CENTER;
public Builder(Context context) {
this.context = context;
}
public Builder setBoundsProvider(BoundsProvider boundsProvider) {
this.boundsProvider = boundsProvider;
return this;
}
public Builder setAlignment(Alignment alignment) {
this.alignment = alignment;
return this;
}
public SpineView build() {
SpineView spineView = new SpineView(context);
spineView.boundsProvider = boundsProvider;
spineView.alignment = alignment;
return spineView;
}
}
private long lastTime = 0;
private float delta = 0;
private float offsetX = 0;
@ -59,12 +88,13 @@ public class SpineView extends View implements Choreographer.FrameCallback {
private float x = 0;
private float y = 0;
SkeletonRenderer renderer = new SkeletonRenderer();
private final SkeletonRenderer renderer = new SkeletonRenderer();
private Bounds computedBounds = new Bounds();
SpineController controller;
BoundsProvider boundsProvider = new SetupPoseBounds();
Bounds computedBounds = new Bounds();
Alignment alignment = Alignment.CENTER;
public SpineView (Context context) {

View File

@ -1,5 +1,9 @@
package com.esotericsoftware.spine.android.bounds;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.FloatArray;
import com.esotericsoftware.spine.Skeleton;
public class Bounds {
private double x;
private double y;
@ -20,6 +24,19 @@ public class Bounds {
this.height = height;
}
public Bounds(Skeleton skeleton) {
Vector2 offset = new Vector2(0, 0);
Vector2 size = new Vector2(0, 0);
FloatArray floatArray = new FloatArray();
skeleton.getBounds(offset, size, floatArray);
x = offset.x;
y = offset.y;
width = size.x;
height = size.y;
}
public double getX() {
return x;
}

View File

@ -8,13 +8,6 @@ public class SetupPoseBounds implements BoundsProvider {
@Override
public Bounds computeBounds(AndroidSkeletonDrawable drawable) {
Vector2 offset = new Vector2(0, 0);
Vector2 size = new Vector2(0, 0);
FloatArray floatArray = new FloatArray();
drawable.getSkeleton().getBounds(offset, size, floatArray);
return new Bounds(offset.x, offset.y, size.x, size.y);
return new Bounds(drawable.getSkeleton());
}
}

View File

@ -0,0 +1,75 @@
package com.esotericsoftware.spine.android.bounds;
import com.esotericsoftware.spine.Animation;
import com.esotericsoftware.spine.SkeletonData;
import com.esotericsoftware.spine.Skin;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
import java.util.Collections;
import java.util.List;
public class SkinAndAnimationBounds implements BoundsProvider {
private final List<String> skins;
private final String animation;
private final double stepTime;
// Constructor
public SkinAndAnimationBounds(List<String> skins, String animation, double stepTime) {
this.skins = (skins == null || skins.isEmpty()) ? Collections.singletonList("default") : skins;
this.animation = animation;
this.stepTime = stepTime;
}
public SkinAndAnimationBounds(List<String> skins, String animation) {
this(skins, animation, 0.1);
}
public SkinAndAnimationBounds(String animation) {
this(Collections.emptyList(), animation, 0.1);
}
@Override
public Bounds computeBounds(AndroidSkeletonDrawable drawable) {
SkeletonData data = drawable.getSkeletonData();
Skin oldSkin = drawable.getSkeleton().getSkin();
Skin customSkin = new Skin("custom-skin");
for (String skinName : skins) {
Skin skin = data.findSkin(skinName);
if (skin == null) continue;
customSkin.addSkin(skin);
}
drawable.getSkeleton().setSkin(customSkin);
drawable.getSkeleton().setToSetupPose();
Animation animation = (this.animation != null) ? data.findAnimation(this.animation) : null;
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
if (animation == null) {
Bounds bounds = new Bounds(drawable.getSkeleton());
minX = bounds.getX();
minY = bounds.getY();
maxX = minX + bounds.getWidth();
maxY = minY + bounds.getHeight();
} else {
drawable.getAnimationState().setAnimation(0, animation, false);
int steps = (int) Math.max( (animation.getDuration() / stepTime), 1.0);
for (int i = 0; i < steps; i++) {
drawable.update(i > 0 ? (float) stepTime : 0);
Bounds bounds = new Bounds(drawable.getSkeleton());
minX = Math.min(minX, bounds.getX());
minY = Math.min(minY, bounds.getY());
maxX = Math.max(maxX, minX + bounds.getWidth());
maxY = Math.max(maxY, minY + bounds.getHeight());
}
}
drawable.getSkeleton().setSkin("default");
drawable.getAnimationState().clearTracks();
if (oldSkin != null) drawable.getSkeleton().setSkin(oldSkin);
drawable.getSkeleton().setToSetupPose();
drawable.update(0);
return new Bounds(minX, minY, maxX - minX, maxY - minY);
}
}