Add spine-android to formatter

This commit is contained in:
Mario Zechner 2024-07-24 20:37:00 +02:00
parent 5de003cb8f
commit 12f11cbbf9
27 changed files with 794 additions and 971 deletions

View File

@ -8,7 +8,8 @@ spotless {
lineEndings 'UNIX'
java {
target 'spine-libgdx/**/*.java'
target 'spine-libgdx/**/*.java',
'spine-android/**/*.java'
eclipse().configFile('formatters/eclipse-formatter.xml')
}

View File

@ -39,40 +39,38 @@ import com.esotericsoftware.spine.android.SpineController;
import com.esotericsoftware.spine.android.SpineView;
public class SimpleAnimationActivity extends AppCompatActivity {
/** @noinspection FieldCanBeLocal*/
private SpineView spineView;
/** @noinspection FieldCanBeLocal*/
private SpineController spineController;
/** @noinspection FieldCanBeLocal */
private SpineView spineView;
/** @noinspection FieldCanBeLocal */
private SpineController spineController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_animation);
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_animation);
// Set up the toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle("Simple Animation");
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
// Set up the toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle("Simple Animation");
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
spineView = findViewById(R.id.spineView);
spineController = new SpineController( controller ->
controller.getAnimationState().setAnimation(0, "walk", true)
);
spineView = findViewById(R.id.spineView);
spineController = new SpineController(controller -> controller.getAnimationState().setAnimation(0, "walk", true));
spineView.setController(spineController);
spineView.loadFromAsset("spineboy.atlas","spineboy-pro.json");
}
spineView.setController(spineController);
spineView.loadFromAsset("spineboy.atlas", "spineboy-pro.json");
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onOptionsItemSelected (MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

14
spine-android/publish.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
#
# 1. Set up PGP key for signing
# 2. Create ~/.gradle/gradle.properties
# 3. Add
# ossrhUsername=<sonatype-token-user-name>
# ossrhPassword=<sonatype-token>
# signing.gnupg.passphrase=<pgp-key-passphrase>
#
# After publishing via this script, log into https://oss.sonatype.org and release it manually after
# checks pass ("Release & Drop").
set -e
./gradlew publishReleasePublicationToSonaTypeRepository --info

View File

@ -40,7 +40,7 @@ dependencies {
androidTestImplementation(libs.androidx.espresso.core)
}
val libraryVersion = "4.2.2-SNAPSHOT" // Update this as needed
val libraryVersion = "4.2.3-SNAPSHOT" // Update this as needed
tasks.register<Jar>("sourceJar") {
archiveClassifier.set("sources")
@ -125,5 +125,6 @@ afterEvaluate {
signing {
useGpgCmd()
sign(publishing.publications["release"])
sign(tasks.getByName("sourceJar"))
}
}

View File

@ -1,3 +1,4 @@
package com.esotericsoftware.android;
import android.content.Context;
@ -10,17 +11,15 @@ import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
/** Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.esotericsoftware.spine.test", appContext.getPackageName());
}
@Test
public void useAppContext () {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.esotericsoftware.spine.test", appContext.getPackageName());
}
}

View File

@ -48,129 +48,111 @@ import com.esotericsoftware.spine.android.utils.SkeletonDataUtils;
import java.io.File;
import java.net.URL;
/**
* A {@link AndroidSkeletonDrawable} bundles loading updating updating an {@link AndroidTextureAtlas}, {@link Skeleton}, and {@link AnimationState}
* into a single easy-to-use class.
/** A {@link AndroidSkeletonDrawable} bundles loading updating updating an {@link AndroidTextureAtlas}, {@link Skeleton}, and
* {@link AnimationState} into a single easy-to-use class.
*
* Use the {@link AndroidSkeletonDrawable#fromAsset(String, String, Context)}, {@link AndroidSkeletonDrawable#fromFile(File, File)},
* or {@link AndroidSkeletonDrawable#fromHttp(URL, URL, File)} methods to construct a {@link AndroidSkeletonDrawable}. To have
* multiple skeleton drawable instances share the same {@link AndroidTextureAtlas} and {@link SkeletonData}, use the constructor.
* Use the {@link AndroidSkeletonDrawable#fromAsset(String, String, Context)},
* {@link AndroidSkeletonDrawable#fromFile(File, File)}, or {@link AndroidSkeletonDrawable#fromHttp(URL, URL, File)} methods to
* construct a {@link AndroidSkeletonDrawable}. To have multiple skeleton drawable instances share the same
* {@link AndroidTextureAtlas} and {@link SkeletonData}, use the constructor.
*
* You can then directly access the {@link AndroidSkeletonDrawable#getAtlas()}, {@link AndroidSkeletonDrawable#getSkeletonData()},
* {@link AndroidSkeletonDrawable#getSkeleton()}, {@link AndroidSkeletonDrawable#getAnimationStateData()}, and {@link AndroidSkeletonDrawable#getAnimationState()}
* to query and animate the skeleton. Use the {@link AnimationState} to queue animations on one or more tracks
* via {@link AnimationState#setAnimation(int, Animation, boolean)} or {@link AnimationState#addAnimation(int, Animation, boolean, float)}.
* {@link AndroidSkeletonDrawable#getSkeleton()}, {@link AndroidSkeletonDrawable#getAnimationStateData()}, and
* {@link AndroidSkeletonDrawable#getAnimationState()} to query and animate the skeleton. Use the {@link AnimationState} to queue
* animations on one or more tracks via {@link AnimationState#setAnimation(int, Animation, boolean)} or
* {@link AnimationState#addAnimation(int, Animation, boolean, float)}.
*
* To update the {@link AnimationState} and apply it to the {@link Skeleton}, call the {@link AndroidSkeletonDrawable#update(float)} function, providing it
* a delta time in seconds to advance the animations.
* To update the {@link AnimationState} and apply it to the {@link Skeleton}, call the
* {@link AndroidSkeletonDrawable#update(float)} function, providing it a delta time in seconds to advance the animations.
*
* To render the current pose of the {@link Skeleton}, use {@link SkeletonRenderer#render(Skeleton)}, {@link SkeletonRenderer#renderToCanvas(Canvas, Array)},
* {@link SkeletonRenderer#renderToBitmap(float, float, int, Skeleton)}, depending on your needs.
*/
* To render the current pose of the {@link Skeleton}, use {@link SkeletonRenderer#render(Skeleton)},
* {@link SkeletonRenderer#renderToCanvas(Canvas, Array)}, {@link SkeletonRenderer#renderToBitmap(float, float, int, Skeleton)},
* depending on your needs. */
public class AndroidSkeletonDrawable {
private final AndroidTextureAtlas atlas;
private final AndroidTextureAtlas atlas;
private final SkeletonData skeletonData;
private final SkeletonData skeletonData;
private final Skeleton skeleton;
private final Skeleton skeleton;
private final AnimationStateData animationStateData;
private final AnimationStateData animationStateData;
private final AnimationState animationState;
private final AnimationState animationState;
/**
* Constructs a new skeleton drawable from the given (possibly shared) {@link AndroidTextureAtlas} and {@link SkeletonData}.
*/
public AndroidSkeletonDrawable(AndroidTextureAtlas atlas, SkeletonData skeletonData) {
this.atlas = atlas;
this.skeletonData = skeletonData;
/** Constructs a new skeleton drawable from the given (possibly shared) {@link AndroidTextureAtlas} and
* {@link SkeletonData}. */
public AndroidSkeletonDrawable (AndroidTextureAtlas atlas, SkeletonData skeletonData) {
this.atlas = atlas;
this.skeletonData = skeletonData;
skeleton = new Skeleton(skeletonData);
animationStateData = new AnimationStateData(skeletonData);
animationState = new AnimationState(animationStateData);
skeleton = new Skeleton(skeletonData);
animationStateData = new AnimationStateData(skeletonData);
animationState = new AnimationState(animationStateData);
skeleton.updateWorldTransform(Skeleton.Physics.none);
}
skeleton.updateWorldTransform(Skeleton.Physics.none);
}
/**
* Updates the {@link AnimationState} using the {@code delta} time given in seconds, applies the
* animation state to the {@link Skeleton} and updates the world transforms of the skeleton
* to calculate its current pose.
*/
public void update(float delta) {
animationState.update(delta);
animationState.apply(skeleton);
/** Updates the {@link AnimationState} using the {@code delta} time given in seconds, applies the animation state to the
* {@link Skeleton} and updates the world transforms of the skeleton to calculate its current pose. */
public void update (float delta) {
animationState.update(delta);
animationState.apply(skeleton);
skeleton.update(delta);
skeleton.updateWorldTransform(Skeleton.Physics.update);
}
skeleton.update(delta);
skeleton.updateWorldTransform(Skeleton.Physics.update);
}
/**
* Get the {@link AndroidTextureAtlas}
*/
public AndroidTextureAtlas getAtlas() {
return atlas;
}
/** Get the {@link AndroidTextureAtlas} */
public AndroidTextureAtlas getAtlas () {
return atlas;
}
/**
* Get the {@link Skeleton}
*/
public Skeleton getSkeleton() {
return skeleton;
}
/** Get the {@link Skeleton} */
public Skeleton getSkeleton () {
return skeleton;
}
/**
* Get the {@link SkeletonData}
*/
public SkeletonData getSkeletonData() {
return skeletonData;
}
/** Get the {@link SkeletonData} */
public SkeletonData getSkeletonData () {
return skeletonData;
}
/**
* Get the {@link AnimationStateData}
*/
public AnimationStateData getAnimationStateData() {
return animationStateData;
}
/** Get the {@link AnimationStateData} */
public AnimationStateData getAnimationStateData () {
return animationStateData;
}
/**
* Get the {@link AnimationState}
*/
public AnimationState getAnimationState() {
return animationState;
}
/** Get the {@link AnimationState} */
public AnimationState getAnimationState () {
return animationState;
}
/**
* Constructs a new skeleton drawable from the {@code atlasFileName} and {@code skeletonFileName} from the the apps resources using {@link Context}.
*
* Throws an exception in case the data could not be loaded.
*/
public static AndroidSkeletonDrawable fromAsset (String atlasFileName, String skeletonFileName, Context context) {
AndroidTextureAtlas atlas = AndroidTextureAtlas.fromAsset(atlasFileName, context);
SkeletonData skeletonData = SkeletonDataUtils.fromAsset(atlas, skeletonFileName, context);
return new AndroidSkeletonDrawable(atlas, skeletonData);
}
/** Constructs a new skeleton drawable from the {@code atlasFileName} and {@code skeletonFileName} from the the apps resources
* using {@link Context}.
*
* Throws an exception in case the data could not be loaded. */
public static AndroidSkeletonDrawable fromAsset (String atlasFileName, String skeletonFileName, Context context) {
AndroidTextureAtlas atlas = AndroidTextureAtlas.fromAsset(atlasFileName, context);
SkeletonData skeletonData = SkeletonDataUtils.fromAsset(atlas, skeletonFileName, context);
return new AndroidSkeletonDrawable(atlas, skeletonData);
}
/**
* Constructs a new skeleton drawable from the {@code atlasFile} and {@code skeletonFile}.
*
* Throws an exception in case the data could not be loaded.
*/
public static AndroidSkeletonDrawable fromFile (File atlasFile, File skeletonFile) {
AndroidTextureAtlas atlas = AndroidTextureAtlas.fromFile(atlasFile);
SkeletonData skeletonData = SkeletonDataUtils.fromFile(atlas, skeletonFile);
return new AndroidSkeletonDrawable(atlas, skeletonData);
}
/** Constructs a new skeleton drawable from the {@code atlasFile} and {@code skeletonFile}.
*
* Throws an exception in case the data could not be loaded. */
public static AndroidSkeletonDrawable fromFile (File atlasFile, File skeletonFile) {
AndroidTextureAtlas atlas = AndroidTextureAtlas.fromFile(atlasFile);
SkeletonData skeletonData = SkeletonDataUtils.fromFile(atlas, skeletonFile);
return new AndroidSkeletonDrawable(atlas, skeletonData);
}
/**
* Constructs a new skeleton drawable from the {@code atlasUrl} and {@code skeletonUrl}.
*
* Throws an exception in case the data could not be loaded.
*/
public static AndroidSkeletonDrawable fromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory) {
AndroidTextureAtlas atlas = AndroidTextureAtlas.fromHttp(atlasUrl, targetDirectory);
SkeletonData skeletonData = SkeletonDataUtils.fromHttp(atlas, skeletonUrl, targetDirectory);
return new AndroidSkeletonDrawable(atlas, skeletonData);
}
/** Constructs a new skeleton drawable from the {@code atlasUrl} and {@code skeletonUrl}.
*
* Throws an exception in case the data could not be loaded. */
public static AndroidSkeletonDrawable fromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory) {
AndroidTextureAtlas atlas = AndroidTextureAtlas.fromHttp(atlasUrl, targetDirectory);
SkeletonData skeletonData = SkeletonDataUtils.fromHttp(atlas, skeletonUrl, targetDirectory);
return new AndroidSkeletonDrawable(atlas, skeletonData);
}
}

View File

@ -40,10 +40,7 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader;
/**
* A class holding an {@link Bitmap} of an {@link AndroidTextureAtlas} page image with it's associated
* blend modes and paints.
*/
/** A class holding an {@link Bitmap} of an {@link AndroidTextureAtlas} page image with it's associated blend modes and paints. */
public class AndroidTexture extends Texture {
private Bitmap bitmap;
private ObjectMap<BlendMode, Paint> paints = new ObjectMap<>();

View File

@ -53,14 +53,11 @@ import android.graphics.Paint;
import android.graphics.BitmapFactory;
import android.os.Build;
/**
* Atlas data loaded from a `.atlas` file and its corresponding `.png` files. For each atlas image,
* a corresponding {@link Bitmap} and {@link Paint} is constructed, which are used when rendering a skeleton
* that uses this atlas.
/** Atlas data loaded from a `.atlas` file and its corresponding `.png` files. For each atlas image, a corresponding
* {@link Bitmap} and {@link Paint} is constructed, which are used when rendering a skeleton that uses this atlas.
*
* Use the static methods {@link AndroidTextureAtlas#fromAsset(String, Context)}, {@link AndroidTextureAtlas#fromFile(File)},
* and {@link AndroidTextureAtlas#fromHttp(URL, File)} to load an atlas.
*/
* Use the static methods {@link AndroidTextureAtlas#fromAsset(String, Context)}, {@link AndroidTextureAtlas#fromFile(File)}, and
* {@link AndroidTextureAtlas#fromHttp(URL, File)} to load an atlas. */
public class AndroidTextureAtlas {
private interface BitmapLoader {
Bitmap load (String path);
@ -94,10 +91,8 @@ public class AndroidTextureAtlas {
}
}
/**
* Returns the first region found with the specified name. This method uses string comparison to find the region, so the
* result should be cached rather than calling this method multiple times.
*/
/** Returns the first region found with the specified name. This method uses string comparison to find the region, so the
* result should be cached rather than calling this method multiple times. */
public @Null AtlasRegion findRegion (String name) {
for (int i = 0, n = regions.size; i < n; i++)
if (regions.get(i).name.equals(name)) return regions.get(i);
@ -112,12 +107,10 @@ public class AndroidTextureAtlas {
return regions;
}
/**
* Loads an {@link AndroidTextureAtlas} from the file {@code atlasFileName} from assets using {@link Context}.
/** Loads an {@link AndroidTextureAtlas} from the file {@code atlasFileName} from assets using {@link Context}.
*
* Throws a {@link RuntimeException} in case the atlas could not be loaded.
*/
public static AndroidTextureAtlas fromAsset(String atlasFileName, Context context) {
* Throws a {@link RuntimeException} in case the atlas could not be loaded. */
public static AndroidTextureAtlas fromAsset (String atlasFileName, Context context) {
TextureAtlasData data = new TextureAtlasData();
AssetManager assetManager = context.getAssets();
@ -138,21 +131,19 @@ public class AndroidTextureAtlas {
}
return new AndroidTextureAtlas(data, path -> {
path = path.startsWith("/") ? path.substring(1) : path;
try (InputStream in = new BufferedInputStream(assetManager.open(path))) {
return BitmapFactory.decodeStream(in);
} catch (Throwable t) {
throw new RuntimeException(t);
}
});
path = path.startsWith("/") ? path.substring(1) : path;
try (InputStream in = new BufferedInputStream(assetManager.open(path))) {
return BitmapFactory.decodeStream(in);
} catch (Throwable t) {
throw new RuntimeException(t);
}
});
}
/**
* Loads an {@link AndroidTextureAtlas} from the file {@code atlasFileName}.
/** Loads an {@link AndroidTextureAtlas} from the file {@code atlasFileName}.
*
* Throws a {@link RuntimeException} in case the atlas could not be loaded.
*/
public static AndroidTextureAtlas fromFile(File atlasFile) {
* Throws a {@link RuntimeException} in case the atlas could not be loaded. */
public static AndroidTextureAtlas fromFile (File atlasFile) {
TextureAtlasData data;
try {
data = loadTextureAtlasData(atlasFile);
@ -169,12 +160,10 @@ public class AndroidTextureAtlas {
});
}
/**
* Loads an {@link AndroidTextureAtlas} from the URL {@code atlasURL}.
/** Loads an {@link AndroidTextureAtlas} from the URL {@code atlasURL}.
*
* Throws a {@link Exception} in case the atlas could not be loaded.
*/
public static AndroidTextureAtlas fromHttp(URL atlasUrl, File targetDirectory) {
* Throws a {@link Exception} in case the atlas could not be loaded. */
public static AndroidTextureAtlas fromHttp (URL atlasUrl, File targetDirectory) {
File atlasFile = HttpUtils.downloadFrom(atlasUrl, targetDirectory);
TextureAtlasData data;
try {
@ -205,20 +194,20 @@ public class AndroidTextureAtlas {
});
}
private static InputStream inputStream(File file) throws Exception {
private static InputStream inputStream (File file) throws Exception {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return Files.newInputStream(file.toPath());
} else {
//noinspection IOStreamConstructor
// noinspection IOStreamConstructor
return new FileInputStream(file);
}
}
private static TextureAtlasData loadTextureAtlasData(File atlasFile) {
private static TextureAtlasData loadTextureAtlasData (File atlasFile) {
TextureAtlasData data = new TextureAtlasData();
FileHandle inputFile = new FileHandle() {
@Override
public InputStream read() {
public InputStream read () {
try {
return new FileInputStream(atlasFile);
} catch (FileNotFoundException e) {

View File

@ -36,21 +36,19 @@ import android.graphics.RectF;
import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.Bone;
/**
* Renders debug information for a {@link AndroidSkeletonDrawable}, like bone locations, to a {@link Canvas}.
* See {@link DebugRenderer#render}.
*/
/** Renders debug information for a {@link AndroidSkeletonDrawable}, like bone locations, to a {@link Canvas}. See
* {@link DebugRenderer#render}. */
public class DebugRenderer {
public void render(AndroidSkeletonDrawable drawable, Canvas canvas, Array<SkeletonRenderer.RenderCommand> commands) {
Paint bonePaint = new Paint();
bonePaint.setColor(android.graphics.Color.BLUE);
bonePaint.setStyle(Paint.Style.FILL);
public void render (AndroidSkeletonDrawable drawable, Canvas canvas, Array<SkeletonRenderer.RenderCommand> commands) {
Paint bonePaint = new Paint();
bonePaint.setColor(android.graphics.Color.BLUE);
bonePaint.setStyle(Paint.Style.FILL);
for (Bone bone : drawable.getSkeleton().getBones()) {
float x = bone.getWorldX();
float y = bone.getWorldY();
canvas.drawRect(new RectF(x - 2.5f, y - 2.5f, x + 2.5f, y + 2.5f), bonePaint);
}
}
for (Bone bone : drawable.getSkeleton().getBones()) {
float x = bone.getWorldX();
float y = bone.getWorldY();
canvas.drawRect(new RectF(x - 2.5f, y - 2.5f, x + 2.5f, y + 2.5f), bonePaint);
}
}
}

View File

@ -50,17 +50,13 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
/**
* Is responsible to transform the {@link Skeleton} with its current pose to {@link SkeletonRenderer.RenderCommand} commands
* and render them to a {@link Canvas}.
*/
/** Is responsible to transform the {@link Skeleton} with its current pose to {@link SkeletonRenderer.RenderCommand} commands and
* render them to a {@link Canvas}. */
public class SkeletonRenderer {
/**
* Stores the vertices, indices, and atlas page index to be used for rendering one or more attachments
* of a {@link Skeleton} to a {@link Canvas}. See the implementation of {@link SkeletonRenderer#render(Skeleton)} and
* {@link SkeletonRenderer#renderToCanvas(Canvas, Array)} on how to use this data to render it to a {@link Canvas}.
*/
/** Stores the vertices, indices, and atlas page index to be used for rendering one or more attachments of a {@link Skeleton}
* to a {@link Canvas}. See the implementation of {@link SkeletonRenderer#render(Skeleton)} and
* {@link SkeletonRenderer#renderToCanvas(Canvas, Array)} on how to use this data to render it to a {@link Canvas}. */
public static class RenderCommand implements Pool.Poolable {
FloatArray vertices = new FloatArray(32);
FloatArray uvs = new FloatArray(32);
@ -90,10 +86,8 @@ public class SkeletonRenderer {
};
private final Array<RenderCommand> commandList = new Array<RenderCommand>();
/**
* Created the {@link RenderCommand} commands from the skeletons current pose.
*/
public Array<RenderCommand> render(Skeleton skeleton) {
/** Created the {@link RenderCommand} commands from the skeletons current pose. */
public Array<RenderCommand> render (Skeleton skeleton) {
Color color = null, skeletonColor = skeleton.getColor();
float r = skeletonColor.r, g = skeletonColor.g, b = skeletonColor.b, a = skeletonColor.a;
@ -228,11 +222,9 @@ public class SkeletonRenderer {
return commandList;
}
/**
* Renders the {@link RenderCommand} commands created from the skeleton current pose to the given {@link Canvas}.
* Does not perform any scaling or fitting.
*/
public void renderToCanvas(Canvas canvas, Array<RenderCommand> commands) {
/** Renders the {@link RenderCommand} commands created from the skeleton current pose to the given {@link Canvas}. Does not
* perform any scaling or fitting. */
public void renderToCanvas (Canvas canvas, Array<RenderCommand> commands) {
for (int i = 0; i < commands.size; i++) {
RenderCommand command = commands.get(i);
@ -241,15 +233,13 @@ public class SkeletonRenderer {
}
}
/**
* Renders the {@link Skeleton} with its current pose to a {@link Bitmap}.
/** Renders the {@link Skeleton} with its current pose to a {@link Bitmap}.
*
* @param width The width of the bitmap in pixels.
* @param height The height of the bitmap in pixels.
* @param bgColor The background color.
* @param skeleton The skeleton to render.
*/
public Bitmap renderToBitmap(float width, float height, int bgColor, Skeleton skeleton) {
* @param width The width of the bitmap in pixels.
* @param height The height of the bitmap in pixels.
* @param bgColor The background color.
* @param skeleton The skeleton to render. */
public Bitmap renderToBitmap (float width, float height, int bgColor, Skeleton skeleton) {
Vector2 offset = new Vector2(0, 0);
Vector2 size = new Vector2(0, 0);
FloatArray floatArray = new FloatArray();
@ -259,7 +249,7 @@ public class SkeletonRenderer {
RectF bounds = new RectF(offset.x, offset.y, offset.x + size.x, offset.y + size.y);
float scale = (1 / (bounds.width() > bounds.height() ? bounds.width() / width : bounds.height() / height));
Bitmap bitmap = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
Bitmap bitmap = Bitmap.createBitmap((int)width, (int)height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();

View File

@ -43,289 +43,245 @@ import com.esotericsoftware.spine.android.callbacks.SpineControllerAfterPaintCal
import com.esotericsoftware.spine.android.callbacks.SpineControllerBeforePaintCallback;
import com.esotericsoftware.spine.android.callbacks.SpineControllerCallback;
/**
* Controls how the skeleton of a {@link SpineView} is animated and rendered.
/** Controls how the skeleton of a {@link SpineView} is animated and rendered.
*
* Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback method is called once. This method can be used
* to set up the initial animation(s) of the skeleton, among other things.
* Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback method is called once. This method can
* be used to set up the initial animation(s) of the skeleton, among other things.
*
* After initialization is complete, the {@link SpineView} is rendered at the screen refresh rate. In each frame,
* the {@link AnimationState} is updated and applied to the {@link Skeleton}.
* After initialization is complete, the {@link SpineView} is rendered at the screen refresh rate. In each frame, the
* {@link AnimationState} is updated and applied to the {@link Skeleton}.
*
* Next, the optionally provided method {@code onBeforeUpdateWorldTransforms} is called, which can modify the
* skeleton before its current pose is calculated using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. After
* {@link Skeleton#updateWorldTransform(Skeleton.Physics)} has completed, the optional {@code onAfterUpdateWorldTransforms} method is
* called, which can modify the current pose before rendering the skeleton.
* Next, the optionally provided method {@code onBeforeUpdateWorldTransforms} is called, which can modify the skeleton before its
* current pose is calculated using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. After
* {@link Skeleton#updateWorldTransform(Skeleton.Physics)} has completed, the optional {@code onAfterUpdateWorldTransforms} method
* is called, which can modify the current pose before rendering the skeleton.
*
* Before the skeleton's current pose is rendered by the {@link SpineView}, the optional {@code onBeforePaint} is called,
* which allows rendering backgrounds or other objects that should go behind the skeleton on the {@link Canvas}. The
* {@link SpineView} then renders the skeleton's current pose and finally calls the optional {@code onAfterPaint}, which
* can render additional objects on top of the skeleton.
* Before the skeleton's current pose is rendered by the {@link SpineView}, the optional {@code onBeforePaint} is called, which
* allows rendering backgrounds or other objects that should go behind the skeleton on the {@link Canvas}. The {@link SpineView}
* then renders the skeleton's current pose and finally calls the optional {@code onAfterPaint}, which can render additional
* objects on top of the skeleton.
*
* The underlying {@link AndroidTextureAtlas}, {@link SkeletonData}, {@link Skeleton}, {@link AnimationStateData}, {@link AnimationState}, and {@link AndroidSkeletonDrawable}
* can be accessed through their respective getters to inspect and/or modify the skeleton and its associated data. Accessing
* this data is only allowed if the {@link SpineView} and its data have been initialized and have not been disposed of yet.
* The underlying {@link AndroidTextureAtlas}, {@link SkeletonData}, {@link Skeleton}, {@link AnimationStateData},
* {@link AnimationState}, and {@link AndroidSkeletonDrawable} can be accessed through their respective getters to inspect and/or
* modify the skeleton and its associated data. Accessing this data is only allowed if the {@link SpineView} and its data have
* been initialized and have not been disposed of yet.
*
* By default, the widget updates and renders the skeleton every frame. The {@code pause} method can be used to pause updating
* and rendering the skeleton. The {@link SpineController#resume()} method resumes updating and rendering the skeleton. The {@link SpineController#isPlaying()} getter
* reports the current state.
*/
* By default, the widget updates and renders the skeleton every frame. The {@code pause} method can be used to pause updating and
* rendering the skeleton. The {@link SpineController#resume()} method resumes updating and rendering the skeleton. The
* {@link SpineController#isPlaying()} getter reports the current state. */
public class SpineController {
/**
* Used to build {@link SpineController} instances.
* */
public static class Builder {
private final SpineControllerCallback onInitialized;
private SpineControllerCallback onBeforeUpdateWorldTransforms;
private SpineControllerCallback onAfterUpdateWorldTransforms;
private SpineControllerBeforePaintCallback onBeforePaint;
private SpineControllerAfterPaintCallback onAfterPaint;
/** Used to build {@link SpineController} instances. */
public static class Builder {
private final SpineControllerCallback onInitialized;
private SpineControllerCallback onBeforeUpdateWorldTransforms;
private SpineControllerCallback onAfterUpdateWorldTransforms;
private SpineControllerBeforePaintCallback onBeforePaint;
private SpineControllerAfterPaintCallback onAfterPaint;
/**
* Instantiate a {@link Builder} used to build a {@link SpineController}, which controls how the skeleton of a {@link SpineView}
* is animated and rendered. Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback
* method is called once. This method can be used to set up the initial animation(s) of the skeleton, among other things.
*
* @param onInitialized Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback
* method is called once. This method can be used to set up the initial animation(s) of the skeleton,
* among other things.
*/
public Builder(SpineControllerCallback onInitialized) {
this.onInitialized = onInitialized;
}
/** Instantiate a {@link Builder} used to build a {@link SpineController}, which controls how the skeleton of a
* {@link SpineView} is animated and rendered. Upon initialization of a {@link SpineView}, the provided
* {@code onInitialized} callback method is called once. This method can be used to set up the initial animation(s) of the
* skeleton, among other things.
*
* @param onInitialized Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback method is
* called once. This method can be used to set up the initial animation(s) of the skeleton, among other things. */
public Builder (SpineControllerCallback onInitialized) {
this.onInitialized = onInitialized;
}
/**
* Sets the {@code onBeforeUpdateWorldTransforms} callback. It is called before the skeleton's current pose is calculated
* using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the skeleton before the pose calculation.
*/
public Builder setOnBeforeUpdateWorldTransforms(SpineControllerCallback onBeforeUpdateWorldTransforms) {
this.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms;
return this;
}
/** Sets the {@code onBeforeUpdateWorldTransforms} callback. It is called before the skeleton's current pose is calculated
* using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the skeleton before the pose
* calculation. */
public Builder setOnBeforeUpdateWorldTransforms (SpineControllerCallback onBeforeUpdateWorldTransforms) {
this.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms;
return this;
}
/**
* Sets the {@code onAfterUpdateWorldTransforms} callback. This method is called after the skeleton's current pose is calculated using
* {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the current pose before rendering the skeleton.
*/
public Builder setOnAfterUpdateWorldTransforms(SpineControllerCallback onAfterUpdateWorldTransforms) {
this.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms;
return this;
}
/** Sets the {@code onAfterUpdateWorldTransforms} callback. This method is called after the skeleton's current pose is
* calculated using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the current pose
* before rendering the skeleton. */
public Builder setOnAfterUpdateWorldTransforms (SpineControllerCallback onAfterUpdateWorldTransforms) {
this.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms;
return this;
}
/**
* Sets the {@code onBeforePaint} callback. It is called before the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering backgrounds or other objects that should go behind the skeleton on the
* {@link Canvas}.
*/
public Builder setOnBeforePaint(SpineControllerBeforePaintCallback onBeforePaint) {
this.onBeforePaint = onBeforePaint;
return this;
}
/** Sets the {@code onBeforePaint} callback. It is called before the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering backgrounds or other objects that should go behind the skeleton on the
* {@link Canvas}. */
public Builder setOnBeforePaint (SpineControllerBeforePaintCallback onBeforePaint) {
this.onBeforePaint = onBeforePaint;
return this;
}
/**
* Sets the {@code onAfterPaint} callback. It is called after the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering additional objects on top of the skeleton.
*/
public Builder setOnAfterPaint(SpineControllerAfterPaintCallback onAfterPaint) {
this.onAfterPaint = onAfterPaint;
return this;
}
/** Sets the {@code onAfterPaint} callback. It is called after the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering additional objects on top of the skeleton. */
public Builder setOnAfterPaint (SpineControllerAfterPaintCallback onAfterPaint) {
this.onAfterPaint = onAfterPaint;
return this;
}
public SpineController build() {
SpineController spineController = new SpineController(onInitialized);
spineController.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms;
spineController.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms;
spineController.onBeforePaint = onBeforePaint;
spineController.onAfterPaint = onAfterPaint;
return spineController;
}
}
public SpineController build () {
SpineController spineController = new SpineController(onInitialized);
spineController.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms;
spineController.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms;
spineController.onBeforePaint = onBeforePaint;
spineController.onAfterPaint = onAfterPaint;
return spineController;
}
}
private final SpineControllerCallback onInitialized;
private @Nullable SpineControllerCallback onBeforeUpdateWorldTransforms;
private @Nullable SpineControllerCallback onAfterUpdateWorldTransforms;
private @Nullable SpineControllerBeforePaintCallback onBeforePaint;
private @Nullable SpineControllerAfterPaintCallback onAfterPaint;
private AndroidSkeletonDrawable drawable;
private boolean playing = true;
private double offsetX = 0;
private double offsetY = 0;
private double scaleX = 1;
private double scaleY = 1;
private final SpineControllerCallback onInitialized;
private @Nullable SpineControllerCallback onBeforeUpdateWorldTransforms;
private @Nullable SpineControllerCallback onAfterUpdateWorldTransforms;
private @Nullable SpineControllerBeforePaintCallback onBeforePaint;
private @Nullable SpineControllerAfterPaintCallback onAfterPaint;
private AndroidSkeletonDrawable drawable;
private boolean playing = true;
private double offsetX = 0;
private double offsetY = 0;
private double scaleX = 1;
private double scaleY = 1;
/**
* Instantiate a {@link SpineController}, which controls how the skeleton of a {@link SpineView} is animated and rendered.
* Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback method is called once.
* This method can be used to set up the initial animation(s) of the skeleton, among other things.
*
* @param onInitialized Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback
* method is called once. This method can be used to set up the initial animation(s) of the skeleton,
* among other things.
*/
public SpineController(SpineControllerCallback onInitialized) {
this.onInitialized = onInitialized;
}
/** Instantiate a {@link SpineController}, which controls how the skeleton of a {@link SpineView} is animated and rendered.
* Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback method is called once. This method
* can be used to set up the initial animation(s) of the skeleton, among other things.
*
* @param onInitialized Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback method is
* called once. This method can be used to set up the initial animation(s) of the skeleton, among other things. */
public SpineController (SpineControllerCallback onInitialized) {
this.onInitialized = onInitialized;
}
protected void init(AndroidSkeletonDrawable drawable) {
this.drawable = drawable;
if (onInitialized != null) {
onInitialized.execute(this);
}
}
protected void init (AndroidSkeletonDrawable drawable) {
this.drawable = drawable;
if (onInitialized != null) {
onInitialized.execute(this);
}
}
/**
* The {@link AndroidTextureAtlas} from which images to render the skeleton are sourced.
*/
public AndroidTextureAtlas getAtlas() {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getAtlas();
}
/** The {@link AndroidTextureAtlas} from which images to render the skeleton are sourced. */
public AndroidTextureAtlas getAtlas () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getAtlas();
}
/**
* The setup-pose data used by the skeleton.
*/
public SkeletonData getSkeletonDate() {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getSkeletonData();
}
/** The setup-pose data used by the skeleton. */
public SkeletonData getSkeletonDate () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getSkeletonData();
}
/**
* The {@link Skeleton}.
*/
public Skeleton getSkeleton() {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getSkeleton();
}
/** The {@link Skeleton}. */
public Skeleton getSkeleton () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getSkeleton();
}
/**
* The mixing information used by the {@link AnimationState}.
*/
public AnimationStateData getAnimationStateData() {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getAnimationStateData();
}
/** The mixing information used by the {@link AnimationState}. */
public AnimationStateData getAnimationStateData () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getAnimationStateData();
}
/**
* The {@link AnimationState} used to manage animations that are being applied to the
* skeleton.
*/
public AnimationState getAnimationState() {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getAnimationState();
}
/** The {@link AnimationState} used to manage animations that are being applied to the skeleton. */
public AnimationState getAnimationState () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getAnimationState();
}
/**
* The {@link AndroidSkeletonDrawable}.
*/
public AndroidSkeletonDrawable getDrawable() {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable;
}
/** The {@link AndroidSkeletonDrawable}. */
public AndroidSkeletonDrawable getDrawable () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable;
}
/**
* Checks if the {@link SpineView} is initialized.
*/
public boolean isInitialized() {
return drawable != null;
}
/** Checks if the {@link SpineView} is initialized. */
public boolean isInitialized () {
return drawable != null;
}
/**
* Checks if the animation is currently playing.
*/
public boolean isPlaying() {
return playing;
}
/** Checks if the animation is currently playing. */
public boolean isPlaying () {
return playing;
}
/**
* Pauses updating and rendering the skeleton.
*/
public void pause() {
if (playing) {
playing = false;
}
}
/** Pauses updating and rendering the skeleton. */
public void pause () {
if (playing) {
playing = false;
}
}
/**
* Resumes updating and rendering the skeleton.
*/
public void resume() {
if (!playing) {
playing = true;
}
}
/** Resumes updating and rendering the skeleton. */
public void resume () {
if (!playing) {
playing = true;
}
}
/**
* Transforms the coordinates given in the {@link SpineView} coordinate system in {@code position} to
* the skeleton coordinate system. See the {@code IKFollowing.kt} example for how to use this
* to move a bone based on user touch input.
*/
public Point toSkeletonCoordinates(Point position) {
int x = position.x;
int y = position.y;
return new Point((int) (x / scaleX - offsetX), (int) (y / scaleY - offsetY));
}
/** Transforms the coordinates given in the {@link SpineView} coordinate system in {@code position} to the skeleton coordinate
* system. See the {@code IKFollowing.kt} example for how to use this to move a bone based on user touch input. */
public Point toSkeletonCoordinates (Point position) {
int x = position.x;
int y = position.y;
return new Point((int)(x / scaleX - offsetX), (int)(y / scaleY - offsetY));
}
/**
* Sets the {@code onBeforeUpdateWorldTransforms} callback. It is called before the skeleton's current pose is calculated
* using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the skeleton before the pose calculation.
*/
public void setOnBeforeUpdateWorldTransforms(@Nullable SpineControllerCallback onBeforeUpdateWorldTransforms) {
this.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms;
}
/** Sets the {@code onBeforeUpdateWorldTransforms} callback. It is called before the skeleton's current pose is calculated
* using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the skeleton before the pose
* calculation. */
public void setOnBeforeUpdateWorldTransforms (@Nullable SpineControllerCallback onBeforeUpdateWorldTransforms) {
this.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms;
}
/**
* Sets the {@code onAfterUpdateWorldTransforms} callback. This method is called after the skeleton's current pose is calculated using
* {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the current pose before rendering the skeleton.
*/
public void setOnAfterUpdateWorldTransforms(@Nullable SpineControllerCallback onAfterUpdateWorldTransforms) {
this.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms;
}
/** Sets the {@code onAfterUpdateWorldTransforms} callback. This method is called after the skeleton's current pose is
* calculated using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the current pose before
* rendering the skeleton. */
public void setOnAfterUpdateWorldTransforms (@Nullable SpineControllerCallback onAfterUpdateWorldTransforms) {
this.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms;
}
/**
* Sets the {@code onBeforePaint} callback. It is called before the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering backgrounds or other objects that should go behind the skeleton on the
* {@link Canvas}.
*/
public void setOnBeforePaint(@Nullable SpineControllerBeforePaintCallback onBeforePaint) {
this.onBeforePaint = onBeforePaint;
}
/** Sets the {@code onBeforePaint} callback. It is called before the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering backgrounds or other objects that should go behind the skeleton on the
* {@link Canvas}. */
public void setOnBeforePaint (@Nullable SpineControllerBeforePaintCallback onBeforePaint) {
this.onBeforePaint = onBeforePaint;
}
/**
* Sets the {@code onAfterPaint} callback. It is called after the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering additional objects on top of the skeleton.
*/
public void setOnAfterPaint(@Nullable SpineControllerAfterPaintCallback onAfterPaint) {
this.onAfterPaint = onAfterPaint;
}
/** Sets the {@code onAfterPaint} callback. It is called after the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering additional objects on top of the skeleton. */
public void setOnAfterPaint (@Nullable SpineControllerAfterPaintCallback onAfterPaint) {
this.onAfterPaint = onAfterPaint;
}
protected void setCoordinateTransform(double offsetX, double offsetY, double scaleX, double scaleY) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.scaleX = scaleX;
this.scaleY = scaleY;
}
protected void setCoordinateTransform (double offsetX, double offsetY, double scaleX, double scaleY) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.scaleX = scaleX;
this.scaleY = scaleY;
}
protected void callOnBeforeUpdateWorldTransforms() {
if (onBeforeUpdateWorldTransforms != null) {
onBeforeUpdateWorldTransforms.execute(this);
}
}
protected void callOnBeforeUpdateWorldTransforms () {
if (onBeforeUpdateWorldTransforms != null) {
onBeforeUpdateWorldTransforms.execute(this);
}
}
protected void callOnAfterUpdateWorldTransforms() {
if (onAfterUpdateWorldTransforms != null) {
onAfterUpdateWorldTransforms.execute(this);
}
}
protected void callOnAfterUpdateWorldTransforms () {
if (onAfterUpdateWorldTransforms != null) {
onAfterUpdateWorldTransforms.execute(this);
}
}
protected void callOnBeforePaint(Canvas canvas) {
if (onBeforePaint != null) {
onBeforePaint.execute(this, canvas);
}
}
protected void callOnBeforePaint (Canvas canvas) {
if (onBeforePaint != null) {
onBeforePaint.execute(this, canvas);
}
}
protected void callOnAfterPaint(Canvas canvas, Array<SkeletonRenderer.RenderCommand> renderCommands) {
if (onAfterPaint != null) {
onAfterPaint.execute(this, canvas, renderCommands);
}
}
protected void callOnAfterPaint (Canvas canvas, Array<SkeletonRenderer.RenderCommand> renderCommands) {
if (onAfterPaint != null) {
onAfterPaint.execute(this, canvas, renderCommands);
}
}
}

View File

@ -51,20 +51,20 @@ import androidx.annotation.NonNull;
import java.io.File;
import java.net.URL;
/**
* A {@link View} to display a Spine skeleton. The skeleton can be loaded from an asset bundle ({@link SpineView#loadFromAssets(String, String, Context, SpineController)}),
* local files ({@link SpineView#loadFromFile(File, File, Context, SpineController)}), URLs ({@link SpineView#loadFromHttp(URL, URL, File, Context, SpineController)}), or a pre-loaded {@link AndroidSkeletonDrawable} using ({@link SpineView#loadFromDrawable(AndroidSkeletonDrawable, Context, SpineController)}).
/** A {@link View} to display a Spine skeleton. The skeleton can be loaded from an asset bundle
* ({@link SpineView#loadFromAssets(String, String, Context, SpineController)}), local files
* ({@link SpineView#loadFromFile(File, File, Context, SpineController)}), URLs
* ({@link SpineView#loadFromHttp(URL, URL, File, Context, SpineController)}), or a pre-loaded {@link AndroidSkeletonDrawable}
* using ({@link SpineView#loadFromDrawable(AndroidSkeletonDrawable, Context, SpineController)}).
*
* The skeleton displayed by a {@link SpineView} can be controlled via a {@link SpineController}.
*
* The size of the widget can be derived from the bounds provided by a {@link BoundsProvider}. If the widget is not sized by the bounds
* computed by the {@link BoundsProvider}, the widget will use the computed bounds to fit the skeleton inside the widget's dimensions.
*/
* The size of the widget can be derived from the bounds provided by a {@link BoundsProvider}. If the widget is not sized by the
* bounds computed by the {@link BoundsProvider}, the widget will use the computed bounds to fit the skeleton inside the widget's
* dimensions. */
public class SpineView extends View implements Choreographer.FrameCallback {
/**
* Used to build {@link SpineView} instances.
* */
/** Used to build {@link SpineView} instances. */
public static class Builder {
private final Context context;
private final SpineController controller;
@ -80,90 +80,72 @@ public class SpineView extends View implements Choreographer.FrameCallback {
private Alignment alignment = Alignment.CENTER;
private ContentMode contentMode = ContentMode.FIT;
/**
* Instantiate a {@link Builder} used to build a {@link SpineView}, which is a {@link View} to display a Spine skeleton.
/** Instantiate a {@link Builder} used to build a {@link SpineView}, which is a {@link View} to display a Spine skeleton.
*
* @param controller The skeleton displayed by a {@link SpineView} can be controlled via a {@link SpineController}.
*/
public Builder(Context context, SpineController controller) {
* @param controller The skeleton displayed by a {@link SpineView} can be controlled via a {@link SpineController}. */
public Builder (Context context, SpineController controller) {
this.context = context;
this.controller = controller;
}
/**
* Loads assets from your app assets for the {@link SpineView} if set. The {@code atlasFileName} specifies the
* `.atlas` file to be loaded for the images used to render the skeleton. The {@code skeletonFileName} specifies either a Skeleton `.json` or
* `.skel` file containing the skeleton data.
*/
public Builder setLoadFromAssets(String atlasFileName, String skeletonFileName) {
/** Loads assets from your app assets for the {@link SpineView} if set. The {@code atlasFileName} specifies the `.atlas`
* file to be loaded for the images used to render the skeleton. The {@code skeletonFileName} specifies either a Skeleton
* `.json` or `.skel` file containing the skeleton data. */
public Builder setLoadFromAssets (String atlasFileName, String skeletonFileName) {
this.atlasFileName = atlasFileName;
this.skeletonFileName = skeletonFileName;
return this;
}
/**
* Loads assets from files for the {@link SpineView} if set. The {@code atlasFile} specifies the `.atlas` file to be loaded for the images used to render
* the skeleton. The {@code skeletonFile} specifies either a Skeleton `.json` or `.skel` file containing the skeleton data.
*/
public Builder setLoadFromFile(File atlasFile, File skeletonFile) {
/** Loads assets from files for the {@link SpineView} if set. The {@code atlasFile} specifies the `.atlas` file to be loaded
* for the images used to render the skeleton. The {@code skeletonFile} specifies either a Skeleton `.json` or `.skel` file
* containing the skeleton data. */
public Builder setLoadFromFile (File atlasFile, File skeletonFile) {
this.atlasFile = atlasFile;
this.skeletonFile = skeletonFile;
return this;
}
/**
* Loads assets from http for the {@link SpineView} if set. The {@code atlasUrl} specifies the `.atlas` url to be loaded for the images used to render
* the skeleton. The {@code skeletonUrl} specifies either a Skeleton `.json` or `.skel` url containing the skeleton data.
*/
public Builder setLoadFromHttp(URL atlasUrl, URL skeletonUrl, File targetDirectory) {
/** Loads assets from http for the {@link SpineView} if set. The {@code atlasUrl} specifies the `.atlas` url to be loaded
* for the images used to render the skeleton. The {@code skeletonUrl} specifies either a Skeleton `.json` or `.skel` url
* containing the skeleton data. */
public Builder setLoadFromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory) {
this.atlasUrl = atlasUrl;
this.skeletonUrl = skeletonUrl;
this.targetDirectory = targetDirectory;
return this;
}
/**
* Uses the {@link AndroidSkeletonDrawable} for the {@link SpineView} if set.
*/
public Builder setLoadFromDrawable(AndroidSkeletonDrawable drawable) {
/** Uses the {@link AndroidSkeletonDrawable} for the {@link SpineView} if set. */
public Builder setLoadFromDrawable (AndroidSkeletonDrawable drawable) {
this.drawable = drawable;
return this;
}
/**
* Get the {@link BoundsProvider} used to compute the bounds of the {@link Skeleton} inside the view.
* The default is {@link SetupPoseBounds}.
*/
public Builder setBoundsProvider(BoundsProvider boundsProvider) {
/** Get the {@link BoundsProvider} used to compute the bounds of the {@link Skeleton} inside the view. The default is
* {@link SetupPoseBounds}. */
public Builder setBoundsProvider (BoundsProvider boundsProvider) {
this.boundsProvider = boundsProvider;
return this;
}
/**
* Get the {@link ContentMode} used to fit the {@link Skeleton} inside the view.
* The default is {@link ContentMode#FIT}.
*/
public Builder setContentMode(ContentMode contentMode) {
/** Get the {@link ContentMode} used to fit the {@link Skeleton} inside the view. The default is {@link ContentMode#FIT}. */
public Builder setContentMode (ContentMode contentMode) {
this.contentMode = contentMode;
return this;
}
/**
* Set the {@link Alignment} used to align the {@link Skeleton} inside the view.
* The default is {@link Alignment#CENTER}
*/
public Builder setAlignment(Alignment alignment) {
/** Set the {@link Alignment} used to align the {@link Skeleton} inside the view. The default is {@link Alignment#CENTER} */
public Builder setAlignment (Alignment alignment) {
this.alignment = alignment;
return this;
}
/**
* Builds a new {@link SpineView}.
/** Builds a new {@link SpineView}.
*
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController} semantics, to allow
* modifying how the skeleton inside the widget is animated and rendered.
*/
public SpineView build() {
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController}
* semantics, to allow modifying how the skeleton inside the widget is animated and rendered. */
public SpineView build () {
SpineView spineView = new SpineView(context, controller);
spineView.boundsProvider = boundsProvider;
spineView.alignment = alignment;
@ -198,201 +180,162 @@ public class SpineView extends View implements Choreographer.FrameCallback {
private Alignment alignment = Alignment.CENTER;
private ContentMode contentMode = ContentMode.FIT;
/**
* Constructs a new {@link SpineView}.
/** Constructs a new {@link SpineView}.
*
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController} semantics, to allow
* modifying how the skeleton inside the widget is animated and rendered.
*/
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController}
* semantics, to allow modifying how the skeleton inside the widget is animated and rendered. */
public SpineView (Context context, SpineController controller) {
super(context);
this.controller = controller;
}
/**
* Constructs a new {@link SpineView} without providing a {@link SpineController}, which you need to provide using
* {@link SpineView#setController(SpineController)}.
*/
/** Constructs a new {@link SpineView} without providing a {@link SpineController}, which you need to provide using
* {@link SpineView#setController(SpineController)}. */
public SpineView (Context context, AttributeSet attrs) {
super(context, attrs);
// Set properties by view id
}
/**
* Constructs a new {@link SpineView} without providing a {@link SpineController}, which you need to provide using
* {@link SpineView#setController(SpineController)}.
*/
/** Constructs a new {@link SpineView} without providing a {@link SpineController}, which you need to provide using
* {@link SpineView#setController(SpineController)}. */
public SpineView (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Set properties by view id
}
/**
* Constructs a new {@link SpineView} from files in your app assets. The {@code atlasFileName} specifies the
* `.atlas` file to be loaded for the images used to render the skeleton. The {@code skeletonFileName} specifies either a Skeleton `.json` or
/** Constructs a new {@link SpineView} from files in your app assets. The {@code atlasFileName} specifies the `.atlas` file to
* be loaded for the images used to render the skeleton. The {@code skeletonFileName} specifies either a Skeleton `.json` or
* `.skel` file containing the skeleton data.
*
* After initialization is complete, the provided {@code controller} is invoked as per the {@link SpineController} semantics, to allow
* modifying how the skeleton inside the widget is animated and rendered.
*/
public static SpineView loadFromAssets(String atlasFileName, String skeletonFileName, Context context, SpineController controller) {
* After initialization is complete, the provided {@code controller} is invoked as per the {@link SpineController} semantics,
* to allow modifying how the skeleton inside the widget is animated and rendered. */
public static SpineView loadFromAssets (String atlasFileName, String skeletonFileName, Context context,
SpineController controller) {
SpineView spineView = new SpineView(context, controller);
spineView.loadFromAsset(atlasFileName, skeletonFileName);
return spineView;
}
/**
* Constructs a new {@link SpineView} from files. The {@code atlasFile} specifies the `.atlas` file to be loaded for the images used to render
* the skeleton. The {@code skeletonFile} specifies either a Skeleton `.json` or `.skel` file containing the skeleton data.
/** Constructs a new {@link SpineView} from files. The {@code atlasFile} specifies the `.atlas` file to be loaded for the
* images used to render the skeleton. The {@code skeletonFile} specifies either a Skeleton `.json` or `.skel` file containing
* the skeleton data.
*
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController} semantics, to allow
* modifying how the skeleton inside the widget is animated and rendered.
*/
public static SpineView loadFromFile(File atlasFile, File skeletonFile, Context context, SpineController controller) {
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController}
* semantics, to allow modifying how the skeleton inside the widget is animated and rendered. */
public static SpineView loadFromFile (File atlasFile, File skeletonFile, Context context, SpineController controller) {
SpineView spineView = new SpineView(context, controller);
spineView.loadFromFile(atlasFile, skeletonFile);
return spineView;
}
/**
* Constructs a new {@link SpineView} from HTTP URLs. The {@code atlasUrl} specifies the `.atlas` url to be loaded for the images used to render
* the skeleton. The {@code skeletonUrl} specifies either a Skeleton `.json` or `.skel` url containing the skeleton data.
/** Constructs a new {@link SpineView} from HTTP URLs. The {@code atlasUrl} specifies the `.atlas` url to be loaded for the
* images used to render the skeleton. The {@code skeletonUrl} specifies either a Skeleton `.json` or `.skel` url containing
* the skeleton data.
*
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController} semantics, to allow
* modifying how the skeleton inside the widget is animated and rendered.
*/
public static SpineView loadFromHttp(URL atlasUrl, URL skeletonUrl, File targetDirectory, Context context, SpineController controller) {
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController}
* semantics, to allow modifying how the skeleton inside the widget is animated and rendered. */
public static SpineView loadFromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory, Context context,
SpineController controller) {
SpineView spineView = new SpineView(context, controller);
spineView.loadFromHttp(atlasUrl, skeletonUrl, targetDirectory);
return spineView;
}
/**
* Constructs a new {@link SpineView} from a {@link AndroidSkeletonDrawable}.
/** Constructs a new {@link SpineView} from a {@link AndroidSkeletonDrawable}.
*
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController} semantics, to allow
* modifying how the skeleton inside the widget is animated and rendered.
*/
public static SpineView loadFromDrawable(AndroidSkeletonDrawable drawable, Context context, SpineController controller) {
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController}
* semantics, to allow modifying how the skeleton inside the widget is animated and rendered. */
public static SpineView loadFromDrawable (AndroidSkeletonDrawable drawable, Context context, SpineController controller) {
SpineView spineView = new SpineView(context, controller);
spineView.loadFromDrawable(drawable);
return spineView;
}
/**
* The same as {@link SpineView#loadFromAssets(String, String, Context, SpineController)}, but can be used after
* instantiating the view via {@link SpineView#SpineView(Context, SpineController)}.
*/
public void loadFromAsset(String atlasFileName, String skeletonFileName) {
loadFrom(() -> AndroidSkeletonDrawable.fromAsset(atlasFileName, skeletonFileName, getContext()));
/** The same as {@link SpineView#loadFromAssets(String, String, Context, SpineController)}, but can be used after instantiating
* the view via {@link SpineView#SpineView(Context, SpineController)}. */
public void loadFromAsset (String atlasFileName, String skeletonFileName) {
loadFrom( () -> AndroidSkeletonDrawable.fromAsset(atlasFileName, skeletonFileName, getContext()));
}
/**
* The same as {@link SpineView#loadFromFile(File, File, Context, SpineController)}, but can be used after
* instantiating the view via {@link SpineView#SpineView(Context, SpineController)}.
*/
public void loadFromFile(File atlasFile, File skeletonFile) {
loadFrom(() -> AndroidSkeletonDrawable.fromFile(atlasFile, skeletonFile));
/** The same as {@link SpineView#loadFromFile(File, File, Context, SpineController)}, but can be used after instantiating the
* view via {@link SpineView#SpineView(Context, SpineController)}. */
public void loadFromFile (File atlasFile, File skeletonFile) {
loadFrom( () -> AndroidSkeletonDrawable.fromFile(atlasFile, skeletonFile));
}
/**
* The same as {@link SpineView#loadFromHttp(URL, URL, File, Context, SpineController)}, but can be used after
* instantiating the view via {@link SpineView#SpineView(Context, SpineController)}.
*/
public void loadFromHttp(URL atlasUrl, URL skeletonUrl, File targetDirectory) {
loadFrom(() -> AndroidSkeletonDrawable.fromHttp(atlasUrl, skeletonUrl, targetDirectory));
/** The same as {@link SpineView#loadFromHttp(URL, URL, File, Context, SpineController)}, but can be used after instantiating
* the view via {@link SpineView#SpineView(Context, SpineController)}. */
public void loadFromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory) {
loadFrom( () -> AndroidSkeletonDrawable.fromHttp(atlasUrl, skeletonUrl, targetDirectory));
}
/**
* The same as {@link SpineView#loadFromDrawable(AndroidSkeletonDrawable, Context, SpineController)}, but can be used after
* instantiating the view via {@link SpineView#SpineView(Context, SpineController)}.
*/
public void loadFromDrawable(AndroidSkeletonDrawable drawable) {
loadFrom(() -> drawable);
/** The same as {@link SpineView#loadFromDrawable(AndroidSkeletonDrawable, Context, SpineController)}, but can be used after
* instantiating the view via {@link SpineView#SpineView(Context, SpineController)}. */
public void loadFromDrawable (AndroidSkeletonDrawable drawable) {
loadFrom( () -> drawable);
}
/**
* Get the {@link SpineController}
*/
public SpineController getController() {
/** Get the {@link SpineController} */
public SpineController getController () {
return controller;
}
/**
* Set the {@link SpineController}. Only do this if you use {@link SpineView#SpineView(Context, AttributeSet)},
* {@link SpineView#SpineView(Context, AttributeSet, int)}, or create the {@link SpineView} in an XML layout.
*/
public void setController(SpineController controller) {
/** Set the {@link SpineController}. Only do this if you use {@link SpineView#SpineView(Context, AttributeSet)},
* {@link SpineView#SpineView(Context, AttributeSet, int)}, or create the {@link SpineView} in an XML layout. */
public void setController (SpineController controller) {
this.controller = controller;
}
/**
* Get the {@link Alignment} used to align the {@link Skeleton} inside the view.
* The default is {@link Alignment#CENTER}
*/
public Alignment getAlignment() {
/** Get the {@link Alignment} used to align the {@link Skeleton} inside the view. The default is {@link Alignment#CENTER} */
public Alignment getAlignment () {
return alignment;
}
/**
* Set the {@link Alignment}.
*/
public void setAlignment(Alignment alignment) {
/** Set the {@link Alignment}. */
public void setAlignment (Alignment alignment) {
this.alignment = alignment;
updateCanvasTransform();
}
/**
* Get the {@link ContentMode} used to fit the {@link Skeleton} inside the view.
* The default is {@link ContentMode#FIT}.
*/
public ContentMode getContentMode() {
/** Get the {@link ContentMode} used to fit the {@link Skeleton} inside the view. The default is {@link ContentMode#FIT}. */
public ContentMode getContentMode () {
return contentMode;
}
/**
* Set the {@link ContentMode}.
*/
public void setContentMode(ContentMode contentMode) {
/** Set the {@link ContentMode}. */
public void setContentMode (ContentMode contentMode) {
this.contentMode = contentMode;
updateCanvasTransform();
}
/**
* Get the {@link BoundsProvider} used to compute the bounds of the {@link Skeleton} inside the view.
* The default is {@link SetupPoseBounds}.
*/
public BoundsProvider getBoundsProvider() {
/** Get the {@link BoundsProvider} used to compute the bounds of the {@link Skeleton} inside the view. The default is
* {@link SetupPoseBounds}. */
public BoundsProvider getBoundsProvider () {
return boundsProvider;
}
/**
* Set the {@link BoundsProvider}.
*/
public void setBoundsProvider(BoundsProvider boundsProvider) {
/** Set the {@link BoundsProvider}. */
public void setBoundsProvider (BoundsProvider boundsProvider) {
this.boundsProvider = boundsProvider;
updateCanvasTransform();
}
/**
* Check if rendering is enabled.
*/
public Boolean isRendering() {
/** Check if rendering is enabled. */
public Boolean isRendering () {
return rendering;
}
/**
* Set to disable or enable rendering. Disable it when the spine view is out of bounds and you want to preserve CPU/GPU resources.
*/
public void setRendering(Boolean rendering) {
/** Set to disable or enable rendering. Disable it when the spine view is out of bounds and you want to preserve CPU/GPU
* resources. */
public void setRendering (Boolean rendering) {
this.rendering = rendering;
}
private void loadFrom(AndroidSkeletonDrawableLoader loader) {
private void loadFrom (AndroidSkeletonDrawableLoader loader) {
Handler mainHandler = new Handler(Looper.getMainLooper());
Thread backgroundThread = new Thread(() -> {
Thread backgroundThread = new Thread( () -> {
final AndroidSkeletonDrawable skeletonDrawable = loader.load();
mainHandler.post(() -> {
mainHandler.post( () -> {
computedBounds = boundsProvider.computeBounds(skeletonDrawable);
updateCanvasTransform();
@ -431,28 +374,30 @@ public class SpineView extends View implements Choreographer.FrameCallback {
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateCanvasTransform();
}
private void updateCanvasTransform() {
private void updateCanvasTransform () {
if (controller == null) {
return;
}
x = (float) (-computedBounds.getX() - computedBounds.getWidth() / 2.0 - (alignment.getX() * computedBounds.getWidth() / 2.0));
y = (float) (-computedBounds.getY() - computedBounds.getHeight() / 2.0 - (alignment.getY() * computedBounds.getHeight() / 2.0));
x = (float)(-computedBounds.getX() - computedBounds.getWidth() / 2.0
- (alignment.getX() * computedBounds.getWidth() / 2.0));
y = (float)(-computedBounds.getY() - computedBounds.getHeight() / 2.0
- (alignment.getY() * computedBounds.getHeight() / 2.0));
switch (contentMode) {
case FIT:
scaleX = scaleY = (float) Math.min(getWidth() / computedBounds.getWidth(), getHeight() / computedBounds.getHeight());
break;
case FILL:
scaleX = scaleY = (float) Math.max(getWidth() / computedBounds.getWidth(), getHeight() / computedBounds.getHeight());
break;
case FIT:
scaleX = scaleY = (float)Math.min(getWidth() / computedBounds.getWidth(), getHeight() / computedBounds.getHeight());
break;
case FILL:
scaleX = scaleY = (float)Math.max(getWidth() / computedBounds.getWidth(), getHeight() / computedBounds.getHeight());
break;
}
offsetX = (float) (getWidth() / 2.0 + (alignment.getX() * getWidth() / 2.0));
offsetY = (float) (getHeight() / 2.0 + (alignment.getY() * getHeight() / 2.0));
offsetX = (float)(getWidth() / 2.0 + (alignment.getX() * getWidth() / 2.0));
offsetY = (float)(getHeight() / 2.0 + (alignment.getY() * getHeight() / 2.0));
controller.setCoordinateTransform(x + offsetX / scaleX, y + offsetY / scaleY, scaleX, scaleY);
}

View File

@ -29,33 +29,24 @@
package com.esotericsoftware.spine.android.bounds;
/**
* How a view should be aligned within another view.
*/
/** How a view should be aligned within another view. */
public enum Alignment {
TOP_LEFT(-1.0f, -1.0f),
TOP_CENTER(0.0f, -1.0f),
TOP_RIGHT(1.0f, -1.0f),
CENTER_LEFT(-1.0f, 0.0f),
CENTER(0.0f, 0.0f),
CENTER_RIGHT(1.0f, 0.0f),
BOTTOM_LEFT(-1.0f, 1.0f),
BOTTOM_CENTER(0.0f, 1.0f),
BOTTOM_RIGHT(1.0f, 1.0f);
TOP_LEFT(-1.0f, -1.0f), TOP_CENTER(0.0f, -1.0f), TOP_RIGHT(1.0f, -1.0f), CENTER_LEFT(-1.0f, 0.0f), CENTER(0.0f,
0.0f), CENTER_RIGHT(1.0f, 0.0f), BOTTOM_LEFT(-1.0f, 1.0f), BOTTOM_CENTER(0.0f, 1.0f), BOTTOM_RIGHT(1.0f, 1.0f);
private final float x;
private final float y;
private final float x;
private final float y;
Alignment(float x, float y) {
this.x = x;
this.y = y;
}
Alignment (float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getX () {
return x;
}
public float getY() {
return y;
}
public float getY () {
return y;
}
}

View File

@ -33,72 +33,69 @@ import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.FloatArray;
import com.esotericsoftware.spine.Skeleton;
/**
* Bounds denoted by the top left corner coordinates {@code x} and {@code y}
* and the {@code width} and {@code height}.
*/
/** Bounds denoted by the top left corner coordinates {@code x} and {@code y} and the {@code width} and {@code height}. */
public class Bounds {
private double x;
private double y;
private double width;
private double height;
private double x;
private double y;
private double width;
private double height;
public Bounds() {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
public Bounds () {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
public Bounds(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public Bounds (double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public Bounds(Skeleton skeleton) {
Vector2 offset = new Vector2(0, 0);
Vector2 size = new Vector2(0, 0);
FloatArray floatArray = new FloatArray();
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);
skeleton.getBounds(offset, size, floatArray);
x = offset.x;
y = offset.y;
width = size.x;
height = size.y;
}
x = offset.x;
y = offset.y;
width = size.x;
height = size.y;
}
public double getX() {
return x;
}
public double getX () {
return x;
}
public void setX(double x) {
this.x = x;
}
public void setX (double x) {
this.x = x;
}
public double getY() {
return y;
}
public double getY () {
return y;
}
public void setY(double y) {
this.y = y;
}
public void setY (double y) {
this.y = y;
}
public double getWidth() {
return width;
}
public double getWidth () {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public void setWidth (double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public double getHeight () {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public void setHeight (double height) {
this.height = height;
}
}

View File

@ -31,10 +31,8 @@ package com.esotericsoftware.spine.android.bounds;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
/**
* A {@link BoundsProvider} that calculates the bounding box of the skeleton based on the visible
* attachments in the setup pose.
*/
/** A {@link BoundsProvider} that calculates the bounding box of the skeleton based on the visible attachments in the setup
* pose. */
public interface BoundsProvider {
Bounds computeBounds(AndroidSkeletonDrawable drawable);
Bounds computeBounds (AndroidSkeletonDrawable drawable);
}

View File

@ -29,16 +29,10 @@
package com.esotericsoftware.spine.android.bounds;
/**
* How a view should be inscribed into another view.
*/
/** How a view should be inscribed into another view. */
public enum ContentMode {
/**
* As large as possible while still containing the source view entirely within the target view.
*/
FIT,
/**
* Fill the target view by distorting the source's aspect ratio.
*/
FILL
/** As large as possible while still containing the source view entirely within the target view. */
FIT,
/** Fill the target view by distorting the source's aspect ratio. */
FILL
}

View File

@ -31,24 +31,22 @@ package com.esotericsoftware.spine.android.bounds;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
/**
* A {@link BoundsProvider} that returns fixed bounds.
*/
/** A {@link BoundsProvider} that returns fixed bounds. */
public class RawBounds implements BoundsProvider {
final Double x;
final Double y;
final Double width;
final Double height;
final Double x;
final Double y;
final Double width;
final Double height;
public RawBounds(Double x, Double y, Double width, Double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public RawBounds (Double x, Double y, Double width, Double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public Bounds computeBounds(AndroidSkeletonDrawable drawable) {
return new Bounds(x, y, width, height);
}
@Override
public Bounds computeBounds (AndroidSkeletonDrawable drawable) {
return new Bounds(x, y, width, height);
}
}

View File

@ -31,14 +31,12 @@ package com.esotericsoftware.spine.android.bounds;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
/**
* A {@link BoundsProvider} that calculates the bounding box of the skeleton based on the visible
* attachments in the setup pose.
*/
/** A {@link BoundsProvider} that calculates the bounding box of the skeleton based on the visible attachments in the setup
* pose. */
public class SetupPoseBounds implements BoundsProvider {
@Override
public Bounds computeBounds(AndroidSkeletonDrawable drawable) {
return new Bounds(drawable.getSkeleton());
}
@Override
public Bounds computeBounds (AndroidSkeletonDrawable drawable) {
return new Bounds(drawable.getSkeleton());
}
}

View File

@ -37,86 +37,75 @@ import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
import java.util.Collections;
import java.util.List;
/**
* A {@link BoundsProvider} that calculates the bounding box needed for a combination of skins
* and an animation.
*/
/** A {@link BoundsProvider} that calculates the bounding box needed for a combination of skins and an animation. */
public class SkinAndAnimationBounds implements BoundsProvider {
private final List<String> skins;
private final String animation;
private final double stepTime;
private final List<String> skins;
private final String animation;
private final double stepTime;
/**
* Constructs a new provider that will use the given {@code skins} and {@code animation} to calculate
* the bounding box of the skeleton. If no skins are given, the default skin is used.
* The {@code stepTime}, given in seconds, defines at what interval the bounds should be sampled
* across the entire animation.
*/
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;
}
/** Constructs a new provider that will use the given {@code skins} and {@code animation} to calculate the bounding box of the
* skeleton. If no skins are given, the default skin is used. The {@code stepTime}, given in seconds, defines at what interval
* the bounds should be sampled across the entire animation. */
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;
}
/**
* Constructs a new provider that will use the given {@code skins} and {@code animation} to calculate
* the bounding box of the skeleton. If no skins are given, the default skin is used.
* The {@code stepTime} has default value 0.1.
*/
public SkinAndAnimationBounds(List<String> skins, String animation) {
this(skins, animation, 0.1);
}
/** Constructs a new provider that will use the given {@code skins} and {@code animation} to calculate the bounding box of the
* skeleton. If no skins are given, the default skin is used. The {@code stepTime} has default value 0.1. */
public SkinAndAnimationBounds (List<String> skins, String animation) {
this(skins, animation, 0.1);
}
/**
* Constructs a new provider that will use the given {@code skins} and {@code animation} to calculate
* the bounding box of the skeleton. The default skin is used. The {@code stepTime} has default value 0.1.
*/
public SkinAndAnimationBounds(String animation) {
this(Collections.emptyList(), animation, 0.1);
}
/** Constructs a new provider that will use the given {@code skins} and {@code animation} to calculate the bounding box of the
* skeleton. The default skin is used. The {@code stepTime} has default value 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();
@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());
}
}
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);
}
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);
}
}

View File

@ -33,5 +33,5 @@ import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
@FunctionalInterface
public interface AndroidSkeletonDrawableLoader {
AndroidSkeletonDrawable load();
AndroidSkeletonDrawable load ();
}

View File

@ -39,5 +39,5 @@ import java.util.List;
@FunctionalInterface
public interface SpineControllerAfterPaintCallback {
void execute (SpineController controller, Canvas canvas, Array<SkeletonRenderer.RenderCommand> commands);
void execute (SpineController controller, Canvas canvas, Array<SkeletonRenderer.RenderCommand> commands);
}

View File

@ -38,5 +38,5 @@ import java.util.List;
@FunctionalInterface
public interface SpineControllerBeforePaintCallback {
void execute (SpineController controller, Canvas canvas);
void execute (SpineController controller, Canvas canvas);
}

View File

@ -33,5 +33,5 @@ import com.esotericsoftware.spine.android.SpineController;
@FunctionalInterface
public interface SpineControllerCallback {
void execute (SpineController controller);
void execute (SpineController controller);
}

View File

@ -41,73 +41,68 @@ import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
/**
* Helper to load http resources.
*/
/** Helper to load http resources. */
public class HttpUtils {
/**
* Download a file from an url into a target directory. It keeps the name from the {@code url}.
* This should NOT be executed on the main run loop.
*/
public static File downloadFrom(URL url, File targetDirectory) throws RuntimeException {
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
OutputStream outputStream = null;
/** Download a file from an url into a target directory. It keeps the name from the {@code url}. This should NOT be executed on
* the main run loop. */
public static File downloadFrom (URL url, File targetDirectory) throws RuntimeException {
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
try {
urlConnection = (HttpURLConnection)url.openConnection();
urlConnection.connect();
if (urlConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new RuntimeException("Failed to connect: HTTP response code " + urlConnection.getResponseCode());
}
if (urlConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new RuntimeException("Failed to connect: HTTP response code " + urlConnection.getResponseCode());
}
inputStream = new BufferedInputStream(urlConnection.getInputStream());
inputStream = new BufferedInputStream(urlConnection.getInputStream());
String atlasUrlPath = url.getPath();
String fileName = atlasUrlPath.substring(atlasUrlPath.lastIndexOf('/') + 1);
File file = new File(targetDirectory, fileName);
String atlasUrlPath = url.getPath();
String fileName = atlasUrlPath.substring(atlasUrlPath.lastIndexOf('/') + 1);
File file = new File(targetDirectory, fileName);
// Create an OutputStream to write to the file
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
outputStream = Files.newOutputStream(file.toPath());
} else {
//noinspection IOStreamConstructor
outputStream = new FileOutputStream(file);
}
// Create an OutputStream to write to the file
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
outputStream = Files.newOutputStream(file.toPath());
} else {
// noinspection IOStreamConstructor
outputStream = new FileOutputStream(file);
}
byte[] buffer = new byte[1024];
int bytesRead;
byte[] buffer = new byte[1024];
int bytesRead;
// Write the input stream to the output stream
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return file;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (outputStream != null) {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
// Nothing we can do
}
}
// Write the input stream to the output stream
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return file;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (outputStream != null) {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
// Nothing we can do
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Nothing we can do
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Nothing we can do
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
}

View File

@ -46,63 +46,57 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
/**
* Helper to load {@link SkeletonData} from assets.
*/
/** Helper to load {@link SkeletonData} from assets. */
public class SkeletonDataUtils {
/**
* Loads a {@link SkeletonData} from the file {@code skeletonFile} in assets using {@link Context}.
* Uses the provided {@link AndroidTextureAtlas} to resolve attachment images.
*
* Throws a {@link RuntimeException} in case the skeleton data could not be loaded.
*/
public static SkeletonData fromAsset(AndroidTextureAtlas atlas, String skeletonFileName, Context context) {
AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas);
/** Loads a {@link SkeletonData} from the file {@code skeletonFile} in assets using {@link Context}. Uses the provided
* {@link AndroidTextureAtlas} to resolve attachment images.
*
* Throws a {@link RuntimeException} in case the skeleton data could not be loaded. */
public static SkeletonData fromAsset (AndroidTextureAtlas atlas, String skeletonFileName, Context context) {
AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas);
SkeletonLoader skeletonLoader;
if (skeletonFileName.endsWith(".json")) {
skeletonLoader = new SkeletonJson(attachmentLoader);
} else {
skeletonLoader = new SkeletonBinary(attachmentLoader);
}
SkeletonLoader skeletonLoader;
if (skeletonFileName.endsWith(".json")) {
skeletonLoader = new SkeletonJson(attachmentLoader);
} else {
skeletonLoader = new SkeletonBinary(attachmentLoader);
}
SkeletonData skeletonData;
SkeletonData skeletonData;
AssetManager assetManager = context.getAssets();
try (InputStream in = new BufferedInputStream(assetManager.open(skeletonFileName))) {
skeletonData = skeletonLoader.readSkeletonData(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
return skeletonData;
}
AssetManager assetManager = context.getAssets();
try (InputStream in = new BufferedInputStream(assetManager.open(skeletonFileName))) {
skeletonData = skeletonLoader.readSkeletonData(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
return skeletonData;
}
/**
* Loads a {@link SkeletonData} from the file {@code skeletonFile}. Uses the provided {@link AndroidTextureAtlas} to resolve attachment images.
*
* Throws a {@link RuntimeException} in case the skeleton data could not be loaded.
*/
public static SkeletonData fromFile(AndroidTextureAtlas atlas, File skeletonFile) {
AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas);
/** Loads a {@link SkeletonData} from the file {@code skeletonFile}. Uses the provided {@link AndroidTextureAtlas} to resolve
* attachment images.
*
* Throws a {@link RuntimeException} in case the skeleton data could not be loaded. */
public static SkeletonData fromFile (AndroidTextureAtlas atlas, File skeletonFile) {
AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas);
SkeletonLoader skeletonLoader;
if (skeletonFile.getPath().endsWith(".json")) {
skeletonLoader = new SkeletonJson(attachmentLoader);
} else {
skeletonLoader = new SkeletonBinary(attachmentLoader);
}
SkeletonLoader skeletonLoader;
if (skeletonFile.getPath().endsWith(".json")) {
skeletonLoader = new SkeletonJson(attachmentLoader);
} else {
skeletonLoader = new SkeletonBinary(attachmentLoader);
}
return skeletonLoader.readSkeletonData(new FileHandle(skeletonFile));
}
return skeletonLoader.readSkeletonData(new FileHandle(skeletonFile));
}
/**
* Loads a {@link SkeletonData} from the URL {@code skeletonURL}. Uses the provided {@link AndroidTextureAtlas} to resolve attachment images.
*
* Throws a {@link RuntimeException} in case the skeleton data could not be loaded.
*/
public static SkeletonData fromHttp(AndroidTextureAtlas atlas, URL skeletonUrl, File targetDirectory) {
File skeletonFile = HttpUtils.downloadFrom(skeletonUrl, targetDirectory);
return fromFile(atlas, skeletonFile);
}
/** Loads a {@link SkeletonData} from the URL {@code skeletonURL}. Uses the provided {@link AndroidTextureAtlas} to resolve
* attachment images.
*
* Throws a {@link RuntimeException} in case the skeleton data could not be loaded. */
public static SkeletonData fromHttp (AndroidTextureAtlas atlas, URL skeletonUrl, File targetDirectory) {
File skeletonFile = HttpUtils.downloadFrom(skeletonUrl, targetDirectory);
return fromFile(atlas, skeletonFile);
}
}

View File

@ -1,17 +1,16 @@
package com.esotericsoftware.android;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
/** Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
@Test
public void addition_isCorrect () {
assertEquals(4, 2 + 2);
}
}

View File

@ -95,7 +95,7 @@ public class Slot {
return color;
}
public void setColor(Color color) {
public void setColor (Color color) {
this.color = color;
}