質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.05%

  • Android

    5721questions

    Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

  • Kotlin

    159questions

    Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

customViewのスケーリング処理がうまくいかない。

受付中

回答 0

投稿

  • 評価
  • クリップ 0
  • VIEW 94

shikasama

score 78

前提・実現したいこと

表示した画像をピンチで拡大縮小できるようにcustomViewを作成しています。
OnScaleGestureListenerでピンチ操作を取得しようとしています。

以下のデバッグログのようにonScale()の呼び出しが一度のみですぐonScaleEnd()が呼ばれているためか、
スケーリングがうまくいきません。

D/CustomScaleImageView: onScaleBegin()
D/CustomScaleImageView: onScale()
D/CustomScaleImageView: updateMatrix() mCurrentScale = 1.6776869 mCurrentX = 103.1402 mCurrentY = 77.35513
D/CustomScaleImageView: onScaleEnd()


どのようにしたらonScale()が連続で呼ばれ、スケーリング処理が正常に行えるでしょうか?

該当のソースコード

以下はImageViewを継承した自作クラスCustomScaleImageViewのイベントリスナ部分とanimator部分です。

override fun onTouchEvent(event: MotionEvent?): Boolean {
    scaleGestureDetector.onTouchEvent(event)
    if(!scaleGestureDetector.isInProgress) {
        gestureDetector.onTouchEvent(event)
    }

    return true
}

private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
    override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
        if(scaling) {
            return false
        }
        // 前の座標
        val previousCurrentX = currentX
        val previousCurrentY = currentY

        currentX = getAdjustedPos(currentX - distanceX, currentScale, imageWidth, width, horizontalFitType)
        currentY = getAdjustedPos(currentY - distanceY, currentScale, imageHeight, height, verticalFitType)

        Log.d(TAG, "onScroll() mCurrentX = $currentX mCurrentY = $currentY")

        if(previousCurrentX != currentX || previousCurrentY != currentY) {
            // 変更あり
            updateMatrix()
        }
        return true
    }

    override fun onDoubleTap(e: MotionEvent?): Boolean {
        Log.d(TAG, "onDoubleTap()")

        var targetScale = 0.0F
        if(currentScale == fitScale) {
            // オリジナル画像のサイズ
            targetScale = 1.0F
        } else {
            targetScale = fitScale
        }
        if(e != null) {
            animator.startScaleAnimation(targetScale, e.x, e.y)
        }

        return true
    }

}

private inner class ScaleGestureListener : OnScaleGestureListener {

    // 前のフォーカスの座標
    private var previousFocusX = 0.0F
    private var previousFocusY = 0.0F

    override fun onScaleBegin(p0: ScaleGestureDetector?): Boolean {
        Log.d(TAG, "onScaleBegin()")

        scaling = true

        // 最初のフォーカス座標を記憶
        p0?.let {
            previousFocusX = p0.focusX
            previousFocusY = p0.focusY
        }

        return true
    }

    override fun onScale(p0: ScaleGestureDetector?): Boolean {
        Log.d(TAG, "onScale()")

        // 前回のイベントでの2点間の距離との比率
        var scaleFactor : Float
        p0?.let {
            scaleFactor = when {
                (currentScale >= maxScale) -> 1F + (p0.scaleFactor - 1F) * maxScale / currentScale * SCALE_RESTRICT_FACTOR
                (currentScale <= minScale) -> 1F + (p0.scaleFactor - 1F) * currentScale / minScale * SCALE_RESTRICT_FACTOR
                else -> p0.scaleFactor
            }

            currentScale *= scaleFactor

            val focusX = p0.focusX
            val focusY = p0.focusY

            currentX = focusX + (currentX - this.previousFocusX) * scaleFactor
            currentY = focusY + (currentY - this.previousFocusY) * scaleFactor
            updateMatrix()

            previousFocusX = focusX
            previousFocusY = focusY
        }

        return true
    }

    override fun onScaleEnd(p0: ScaleGestureDetector?) {
        Log.d(TAG, "onScaleEnd()")

        val targetScale = ensureRange(currentScale, minScale, maxScale)
        animator.startScaleAnimation(targetScale, previousFocusX, previousFocusY)
    }
}

private inner class TransformAnimator {
    // アニメーション開始位置
    private var startScale   = 0.0F
    private var startX       = 0.0F
    private var startY       = 0.0F

    // アニメーション目標位置
    private var targetScale  = 0.0F
    private var targetX      = 0.0F
    private var targetY      = 0.0F

    // 開始と目標の差
    private var diffScale    = 0.0F
    private var diffX        = 0.0F
    private var diffY        = 0.0F

    // アニメーション実行時間
    private var duration     = ANIMATION_DURATION
    // アニメーション開始時間
    private var startTime : Long = 0
    // アニメーション実行状態
    private var isAnimating  = false

    internal fun startAnimation(targetScale: Float, targetX : Float, targetY : Float) {
        if(isAnimating) {
            // すでにアニメーションが実行中の場合はなにもしません
            return
        }

        if(targetScale == currentScale && targetX == currentX && targetY == currentY) {
            // 現在の位置とスケーリング後の位置が同じ場合はなにもしません
            return
        }

        // アニメーションの開始位置を設定
        startScale   = currentScale
        startX       = currentX
        startY       = currentY

        // アニメーションの目標位置を設定
        this.targetScale = targetScale
        this.targetX     = targetX
        this.targetY     = targetY

        // 開始位置と目標位置の差分を取得
        diffScale    = targetScale - startScale
        diffX        = targetX - startX
        diffY        = targetY - startY

        // 現在時刻(ミリ秒単位)を開始時刻に設定
        startTime = System.currentTimeMillis()

        // アニメーション実行状態
        isAnimating = true

        // 再描画する(onDrawを呼ぶ)
        invalidate()

        Log.d(TAG, "TransformAnimator.startAnimation() invalidate()")
    }

    internal fun startScaleAnimation(targetScale : Float, pivotX : Float, pivotY : Float) {
        // スケーリング後の位置を計算
        val diffScaleFactor = targetScale / currentScale - 1.0F
        var targetX = currentX + (currentX - pivotX) * diffScaleFactor
        var targetY = currentY + (currentY - pivotY) * diffScaleFactor
        // 調整後の位置を取得
        targetX = getAdjustedPos(targetX, targetScale, imageWidth, width, horizontalFitType)
        targetY = getAdjustedPos(targetY, targetScale, imageHeight, height, verticalFitType)

        // アニメーションを開始する
        startAnimation(targetScale, targetX, targetY)
    }

    internal fun animate() {
        if(!isAnimating) {
            // アニメーション実行状態でない場合
            return
        }

        val diffTime = System.currentTimeMillis() - startTime
        if(diffTime >= duration) {
            // アニメーション実行時間を過ぎた場合はアニメーションを終了させる

            isAnimating  = false
            currentScale = targetScale
            currentX     = targetX
            currentY     = targetY
        } else {
            // アニメーション実行時間内の場合

            // 進捗度
            val progress = diffTime.toFloat() / duration
            // 進捗状況に合わせて移動位置を計算します
            currentScale = startScale + (diffScale * progress)
            currentX     = startX + (diffX * progress)
            currentY     = startY + (diffY * progress)
        }

        Log.d(TAG, "TransformAnimator.animate() mCurrentScale = $currentScale mCurrentX = $currentX mCurrentY = $currentY")
        // 移動位置を反映させる
        updateMatrix()
        // 再描画する
        invalidate()
    }
}

private fun updateMatrix() {
    // 初期イメージのcurrentScale倍にする
    _matrix.setScale(currentScale, currentScale)
    // 移動する
    _matrix.postTranslate(currentX, currentY)
    // imegeViewにmatrixを設定する
    imageMatrix = _matrix
    Log.d(TAG, "updateMatrix() mCurrentScale = $currentScale mCurrentX = $currentX mCurrentY = $currentY")
}

補足情報(言語/FW/ツール等のバージョンなど)

android studio 3.0.1
kotlin 1.2.10

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 91.05%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    SKActionが終わった後に何か動作をさせたい時

    SKActionが実行され、そのアクションが終わった時に呼ばれる関数というものは存在しますか?

  • 解決済

    ホームボタン押下時のアニメーションの処理

    swiftに関する質問です。 現在下記のように20秒かけてバーを減少さるアニメーションを行っています。 ホームボタン押下時にこのバーを一度停止したいためAppDelegate.

  • 解決済

    NSTimerとUIView.animateWithDurationとの時間の同期

    swiftに関する質問です。 現在、NSTimerを使用してLabelに20秒から0秒までカウントダウンするような表示をしている状態です。 その表示とは別のUIViewでa

  • 解決済

    Androidでタップして図形を複数描画するアプリケーション

    ご覧頂き ありがとうございます。 現在タップをした座標に円形を描画するアプリケーションを制作しています。 そこでつまづいてしまっている部分が、今回ご教示いただきたい内容

  • 解決済

    animate中の処理

    とあるシステムで、メニューの開閉を改修しています。 現在は、クリック連打対応としてフラグを用意し、 animate開始時にフラグON、animate完了時にフラグOFFにするこ

  • 解決済

    【swift】MBProgressHUDの使い方

    前提・実現したいこと https://github.com/jdg/MBProgressHUD のライブラリを使っているのですが, プログレスの表示から表示を終了するまでの基

  • 受付中

    kotlinでparcelable

    ArrayList<Any>にparcelableを実装する方法が分かりません。 作成したリストの中にはString型と独自に作成したクラス(UploadedItem)が入っていま

  • 解決済

    Unity5.6 パスワード入力実装について

    Unity5.6を使用しています。 4桁のパスワード機能を実装しようと思っております。 GUIは完成して、あとはプログラムのところなのですが わからいない点が何点かあり、質問さ

同じタグがついた質問を見る

  • Android

    5721questions

    Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

  • Kotlin

    159questions

    Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。