【Android】 대량 프레임 수의 파라 파라 만화 애니메이션을 Sprite를 사용하여 1 장으로 정리

소개



「화상을 애니메이션시키고 싶습니다! 파라파라 만화이므로 간단하게 할 수 있다고 생각합니다!」
라고 말하는 것은 자주 있다고 생각합니다. …하지만,

「이것이 이미지입니다! 100장 있습니다!」
그렇다면 이야기는 조금 다른 번역입니다.

anim.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true"
    android:visible="true">

    <item
        android:drawable="@drawable/yoshino000"
        android:duration="42" />
    <item
        android:drawable="@drawable/yoshino001"
        android:duration="42" />
    <item
        android:drawable="@drawable/yoshino002"
        android:duration="42" />
    <item
        android:drawable="@drawable/yoshino003"
        android:duration="42" />
    <item
        android:drawable="@drawable/yoshino004"
        android:duration="42" />

        <!-- はてしない中略 -->

    <item
        android:drawable="@drawable/yoshino108"
        android:duration="42" />

</animation-list>

아니 길고 전망이 나쁘고 이미지 파일 너무 많아서 엉망이 되어 무엇보다 아름답지 않다.
라고 하는 것으로 이번은 Sprite를 사용해 1장의 화상으로부터 애니메이션을 만들어 가고 싶습니다.

Sprite?



게임이나 Web등에서, 화상을 몇번이나 몇번이나 매번 로드하면도 붙어 있으므로, 로드 횟수는 가능하면 적게 하고 싶다.
거기서, 1화면에서 이용하는 화상을 모두 1장의 화상에 정리해, 화상 중에서 일부만을 잘라 사용한다고 하는 생각이 있습니다.

* WidgetWorx에서 빌렸습니다 htps //w w. 우드와 rx. 코 m / sp 리테 b /

RPG 트쿨이나 슈퍼 정남을 한 적이 있는 분이라면 친숙할지도 모릅니다.
이번에는 이 방식을 이용하여 프레임 수가 많은 애니메이션을 1장의 스프라이트로 표현해 가고 싶습니다.

맞춤 보기 만들기



커스텀 뷰에 대해서는 자세하게 써 있는 기사가 여러가지 있으므로 세세하게는 할애합니다만, 기본적으로는 생성자를 제대로 써, onDraw로 묘화하고 싶은 것을 지정하면 대체로 움직입니다.
본래는 파라미터의 지정등은 attr를 사용해 xml로부터 지정, 동적으로 변경시키고 싶은 것에 관해서는 DataBinding를 사용해 ViewModel로부터 지정하는 것이 예쁜 것일지도 모릅니다만, 이번은 간결하게 하기 위해서 init 메소드를 만들고 값을 넣어갑니다.

SpriteAnimation.java
    /**
     * 初期化
     *
     * @param resId アニメーションさせるsprite画像のリソースID
     * @param sizeX 1列のコマ数
     * @param size 全体のコマ数
     * @param interval 1フレームの間隔
     * @param repeatTimes 繰り返し回数。無限ループの場合は0を指定
     * @param delay 開始まで何ミリ秒遅らせるか
     */
    public void init(
            final int resId,
            final int sizeX,
            final int size,
            final int interval,
            final int repeatTimes,
            final int delay) {
        mSprite = BitmapFactory.decodeResource(getResources(), resId, null);
        mSizeX = sizeX;
        mSize = size - 1;
        mPixWidth = mSprite.getWidth() / sizeX;
        mPixHeight = mSprite.getHeight() / ((size + sizeX - 1) / sizeX);
        mWillRepeat = repeatTimes;

        mCurrentFrame = 0;
        mRepeated = 0;

        mCurrentRect = new Rect();
        mDestRect = new Rect();

        mIsStarted = true;
        final Handler handler = new Handler();
        mRunnable = () -> {
            if (repeatTimes != 0 && mRepeated >= repeatTimes) {
                handler.removeCallbacks(mRunnable);
                mRunnable = null;
            } else {
                invalidate();
                handler.postDelayed(mRunnable, interval);
            }
        };

        handler.postDelayed(mRunnable, delay);
    }

Bitmap이나 Rect의 인스턴스화는 onDraw()로 매번 하면 낭비이므로 먼저 단번에 갑니다.

invalidate()를 호출하면 onDraw가 불리기 때문에, 묘화 마다의 처리는 여기에 써 갑니다.

SpriteAnimation.java
    /**
     * {@inheritDoc}
     */
    @Override
    protected void onDraw(final Canvas canvas) {
        super.onDraw(canvas);

        if (!mIsStarted) {
            return;
        }

        if (mWillRepeat != 0 && mRepeated >= mWillRepeat) {
            mCurrentFrame = mSize;
        }

        // 描画する範囲を決める
        final int col = mCurrentFrame % mSizeX;
        final int row = mCurrentFrame / mSizeX;

        mCurrentRect.set(
                col * mPixWidth,
                row * mPixHeight,
                (col + 1) * mPixWidth,
                (row + 1) * mPixHeight);

        mDestRect.set(0, 0, getWidth(), getHeight());

        canvas.drawBitmap(mSprite, mCurrentRect, mDestRect, null);

        mCurrentFrame++;
        if (mCurrentFrame >= mSize) {
            mRepeated++;
            if (mWillRepeat == 0 || mWillRepeat > mRepeated) {
                mCurrentFrame = 0;
            }
        }
    }

먼저 로드한 스프라이트 시트에서 그 컷으로 그리는 범위를 잘라 갑니다.

지금의 프레임과 가로폭으로부터 잘라내는 범위의 좌상의 좌표를 계산해, 거기로부터 화상의 폭분 취하고 있는 느낌이군요.
onDraw는 invalidate()에서 호출하지 않아도 화면 스크롤 등으로 이미지가 표시될 때마다 호출되어 버리므로 주의가 필요합니다. 애니메이션 멈춘 생각이 스크롤하면 이상한 이미지에! ? 무슨 일이 될 수 있기 때문에.

결론



이번에는 스프라이트를 사용하여 애니메이션을 만들어 보았습니다만, 솔직히 안드로이드에서 지원되는 몇몇 애니메이션 클래스를 사용하는 것이 쉽고 동작도 보장됩니다.
다만, 너무 많은 이미지 파일을 추가해야 하는 것은 싫다고 하는 분은, 이런 방법도 있으므로 참고로 해 보면 어떻습니까.

추가



이 구현이라면 단말에 따라 애니메이션이 기괴한 움직임을 할 수 있습니다.
정확하게는 이미지의 한 컷의 크기가 16px의 배수가 아니면 컷마다 묘화 범위가 미묘하게 어긋나 버리는 문제가 일어나 버립니다.
이것은 안드로이드의 dp가 mdpi(160dpi)를 기점으로 배율로 정해져 있기 때문이라고 합니다.
xdpi의 경우 1px=2.0dp 등. ldpi(1px=0.75dp)라든지는 모른다
애니메이션이 흔들리고 떨리는 분은 그 곳을 고려해보십시오.

참고로



[Android] Canvas 클리어하고 다시 그리기
Sprite Animation in Android
Android Game Programming 4. Our First Sprite

좋은 웹페이지 즐겨찾기