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' lineEndings 'UNIX'
java { java {
target 'spine-libgdx/**/*.java' target 'spine-libgdx/**/*.java',
'spine-android/**/*.java'
eclipse().configFile('formatters/eclipse-formatter.xml') eclipse().configFile('formatters/eclipse-formatter.xml')
} }

View File

@ -59,9 +59,7 @@ public class SimpleAnimationActivity extends AppCompatActivity {
} }
spineView = findViewById(R.id.spineView); spineView = findViewById(R.id.spineView);
spineController = new SpineController( controller -> spineController = new SpineController(controller -> controller.getAnimationState().setAnimation(0, "walk", true));
controller.getAnimationState().setAnimation(0, "walk", true)
);
spineView.setController(spineController); spineView.setController(spineController);
spineView.loadFromAsset("spineboy.atlas", "spineboy-pro.json"); spineView.loadFromAsset("spineboy.atlas", "spineboy-pro.json");

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) 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") { tasks.register<Jar>("sourceJar") {
archiveClassifier.set("sources") archiveClassifier.set("sources")
@ -125,5 +125,6 @@ afterEvaluate {
signing { signing {
useGpgCmd() useGpgCmd()
sign(publishing.publications["release"]) sign(publishing.publications["release"])
sign(tasks.getByName("sourceJar"))
} }
} }

View File

@ -1,3 +1,4 @@
package com.esotericsoftware.android; package com.esotericsoftware.android;
import android.content.Context; import android.content.Context;
@ -10,11 +11,9 @@ import org.junit.runner.RunWith;
import static org.junit.Assert.*; 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) @RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest { public class ExampleInstrumentedTest {
@Test @Test

View File

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

View File

@ -40,10 +40,7 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode; import android.graphics.PorterDuffXfermode;
import android.graphics.Shader; 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 { public class AndroidTexture extends Texture {
private Bitmap bitmap; private Bitmap bitmap;
private ObjectMap<BlendMode, Paint> paints = new ObjectMap<>(); private ObjectMap<BlendMode, Paint> paints = new ObjectMap<>();

View File

@ -53,14 +53,11 @@ import android.graphics.Paint;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Build; import android.os.Build;
/** /** Atlas data loaded from a `.atlas` file and its corresponding `.png` files. For each atlas image, a corresponding
* Atlas data loaded from a `.atlas` file and its corresponding `.png` files. For each atlas image, * {@link Bitmap} and {@link Paint} is constructed, which are used when rendering a skeleton that uses this atlas.
* 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)}, * Use the static methods {@link AndroidTextureAtlas#fromAsset(String, Context)}, {@link AndroidTextureAtlas#fromFile(File)}, and
* and {@link AndroidTextureAtlas#fromHttp(URL, File)} to load an atlas. * {@link AndroidTextureAtlas#fromHttp(URL, File)} to load an atlas. */
*/
public class AndroidTextureAtlas { public class AndroidTextureAtlas {
private interface BitmapLoader { private interface BitmapLoader {
Bitmap load (String path); 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
* 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. */
* result should be cached rather than calling this method multiple times.
*/
public @Null AtlasRegion findRegion (String name) { public @Null AtlasRegion findRegion (String name) {
for (int i = 0, n = regions.size; i < n; i++) for (int i = 0, n = regions.size; i < n; i++)
if (regions.get(i).name.equals(name)) return regions.get(i); if (regions.get(i).name.equals(name)) return regions.get(i);
@ -112,11 +107,9 @@ public class AndroidTextureAtlas {
return regions; 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. * Throws a {@link RuntimeException} in case the atlas could not be loaded. */
*/
public static AndroidTextureAtlas fromAsset (String atlasFileName, Context context) { public static AndroidTextureAtlas fromAsset (String atlasFileName, Context context) {
TextureAtlasData data = new TextureAtlasData(); TextureAtlasData data = new TextureAtlasData();
AssetManager assetManager = context.getAssets(); AssetManager assetManager = context.getAssets();
@ -147,11 +140,9 @@ public class AndroidTextureAtlas {
}); });
} }
/** /** 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. * Throws a {@link RuntimeException} in case the atlas could not be loaded. */
*/
public static AndroidTextureAtlas fromFile (File atlasFile) { public static AndroidTextureAtlas fromFile (File atlasFile) {
TextureAtlasData data; TextureAtlasData data;
try { try {
@ -169,11 +160,9 @@ 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. * Throws a {@link Exception} in case the atlas could not be loaded. */
*/
public static AndroidTextureAtlas fromHttp (URL atlasUrl, File targetDirectory) { public static AndroidTextureAtlas fromHttp (URL atlasUrl, File targetDirectory) {
File atlasFile = HttpUtils.downloadFrom(atlasUrl, targetDirectory); File atlasFile = HttpUtils.downloadFrom(atlasUrl, targetDirectory);
TextureAtlasData data; TextureAtlasData data;

View File

@ -36,10 +36,8 @@ import android.graphics.RectF;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.Bone; import com.esotericsoftware.spine.Bone;
/** /** Renders debug information for a {@link AndroidSkeletonDrawable}, like bone locations, to a {@link Canvas}. See
* Renders debug information for a {@link AndroidSkeletonDrawable}, like bone locations, to a {@link Canvas}. * {@link DebugRenderer#render}. */
* See {@link DebugRenderer#render}.
*/
public class DebugRenderer { public class DebugRenderer {
public void render (AndroidSkeletonDrawable drawable, Canvas canvas, Array<SkeletonRenderer.RenderCommand> commands) { public void render (AndroidSkeletonDrawable drawable, Canvas canvas, Array<SkeletonRenderer.RenderCommand> commands) {

View File

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

View File

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

View File

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

View File

@ -29,19 +29,10 @@
package com.esotericsoftware.spine.android.bounds; 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 { public enum Alignment {
TOP_LEFT(-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,
TOP_CENTER(0.0f, -1.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_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 x;
private final float y; private final float y;

View File

@ -33,10 +33,7 @@ import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.FloatArray;
import com.esotericsoftware.spine.Skeleton; 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 { public class Bounds {
private double x; private double x;
private double y; private double y;

View File

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

View File

@ -29,16 +29,10 @@
package com.esotericsoftware.spine.android.bounds; 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 { public enum ContentMode {
/** /** As large as possible while still containing the source view entirely within the target view. */
* As large as possible while still containing the source view entirely within the target view.
*/
FIT, FIT,
/** /** Fill the target view by distorting the source's aspect ratio. */
* Fill the target view by distorting the source's aspect ratio.
*/
FILL FILL
} }

View File

@ -31,9 +31,7 @@ package com.esotericsoftware.spine.android.bounds;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable; 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 { public class RawBounds implements BoundsProvider {
final Double x; final Double x;
final Double y; final Double y;

View File

@ -31,10 +31,8 @@ package com.esotericsoftware.spine.android.bounds;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable; 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
* A {@link BoundsProvider} that calculates the bounding box of the skeleton based on the visible * pose. */
* attachments in the setup pose.
*/
public class SetupPoseBounds implements BoundsProvider { public class SetupPoseBounds implements BoundsProvider {
@Override @Override

View File

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

View File

@ -41,14 +41,10 @@ import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
/** /** Helper to load http resources. */
* Helper to load http resources.
*/
public class HttpUtils { 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
* Download a file from an url into a target directory. It keeps the name from the {@code url}. * the main run loop. */
* This should NOT be executed on the main run loop.
*/
public static File downloadFrom (URL url, File targetDirectory) throws RuntimeException { public static File downloadFrom (URL url, File targetDirectory) throws RuntimeException {
HttpURLConnection urlConnection = null; HttpURLConnection urlConnection = null;
InputStream inputStream = null; InputStream inputStream = null;
@ -110,4 +106,3 @@ public class HttpUtils {
} }
} }
} }

View File

@ -46,17 +46,13 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
/** /** Helper to load {@link SkeletonData} from assets. */
* Helper to load {@link SkeletonData} from assets.
*/
public class SkeletonDataUtils { public class SkeletonDataUtils {
/** /** Loads a {@link SkeletonData} from the file {@code skeletonFile} in assets using {@link Context}. Uses the provided
* Loads a {@link SkeletonData} from the file {@code skeletonFile} in assets using {@link Context}. * {@link AndroidTextureAtlas} to resolve attachment images.
* Uses the provided {@link AndroidTextureAtlas} to resolve attachment images.
* *
* Throws a {@link RuntimeException} in case the skeleton data could not be loaded. * Throws a {@link RuntimeException} in case the skeleton data could not be loaded. */
*/
public static SkeletonData fromAsset (AndroidTextureAtlas atlas, String skeletonFileName, Context context) { public static SkeletonData fromAsset (AndroidTextureAtlas atlas, String skeletonFileName, Context context) {
AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas); AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas);
@ -78,11 +74,10 @@ public class SkeletonDataUtils {
return skeletonData; return skeletonData;
} }
/** /** Loads a {@link SkeletonData} from the file {@code skeletonFile}. Uses the provided {@link AndroidTextureAtlas} to resolve
* Loads a {@link SkeletonData} from the file {@code skeletonFile}. Uses the provided {@link AndroidTextureAtlas} to resolve attachment images. * attachment images.
* *
* Throws a {@link RuntimeException} in case the skeleton data could not be loaded. * Throws a {@link RuntimeException} in case the skeleton data could not be loaded. */
*/
public static SkeletonData fromFile (AndroidTextureAtlas atlas, File skeletonFile) { public static SkeletonData fromFile (AndroidTextureAtlas atlas, File skeletonFile) {
AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas); AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas);
@ -96,11 +91,10 @@ public class SkeletonDataUtils {
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
* Loads a {@link SkeletonData} from the URL {@code skeletonURL}. Uses the provided {@link AndroidTextureAtlas} to resolve attachment images. * attachment images.
* *
* Throws a {@link RuntimeException} in case the skeleton data could not be loaded. * Throws a {@link RuntimeException} in case the skeleton data could not be loaded. */
*/
public static SkeletonData fromHttp (AndroidTextureAtlas atlas, URL skeletonUrl, File targetDirectory) { public static SkeletonData fromHttp (AndroidTextureAtlas atlas, URL skeletonUrl, File targetDirectory) {
File skeletonFile = HttpUtils.downloadFrom(skeletonUrl, targetDirectory); File skeletonFile = HttpUtils.downloadFrom(skeletonUrl, targetDirectory);
return fromFile(atlas, skeletonFile); return fromFile(atlas, skeletonFile);

View File

@ -1,14 +1,13 @@
package com.esotericsoftware.android; package com.esotericsoftware.android;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; 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 { public class ExampleUnitTest {
@Test @Test
public void addition_isCorrect () { public void addition_isCorrect () {