使用RxJava實現ImageView的拖動、旋轉和縮放

cdj發表於2019-04-28

本文介紹一種使用Rxjava實現圖片互動操作的方法。支援單指拖動,雙指旋轉縮放,效果如下:

使用RxJava實現ImageView的拖動、旋轉和縮放

自定義View

首先自定義TrsImageView繼承ImageView,設定ScaleTypeMatrix,我們使用矩陣計算最終的translate, rotate和scale。

public class TrsImageView extends ImageView {

    public TrsImageView(Context context) {
        super(context);
        init();
    }
    
    private void init() {
        Matrix matrix = new Matrix();
        setScaleType(ScaleType.MATRIX);
        setImageMatrix(matrix);
    }
}
複製程式碼

建立touch事件Observable

create方法建立Observable,只考慮單指和雙指的情況,用share操作符使Observable可以被多次訂閱。自定義Event類,儲存觸控事件id和位置。

    private void init() {
        ...
        Observable<Event> touchStream = Observable.create((ObservableEmitter<Event> emitter) -> {
            setOnTouchListener((v, event) -> {
                int pointerCount = event.getPointerCount();
                if (pointerCount == 1) {
                    Event e = new Event();
                    e.action = event.getActionMasked();
                    e.p1 = new Vector(event.getX(), event.getY());
                    emitter.onNext(e);
                } else if (pointerCount == 2) {
                    Event e = new Event();
                    e.action = event.getActionMasked();
                    e.p1 = new Vector(event.getX(0), event.getY(0));
                    e.p2 = new Vector(event.getX(1), event.getY(1));
                    emitter.onNext(e);
                }
                return true;
            });
        }).share();
    }
複製程式碼

使用filter操作符獲取不同觸控事件的Observable

    private void init() {
        ...
        Observable<Event> pointer1Down = touchStream.filter(e -> e.action == MotionEvent.ACTION_DOWN);
        Observable<Event> pointer2Down = touchStream.filter(e -> e.action == MotionEvent.ACTION_POINTER_DOWN);
        Observable<Event> pointerMove = touchStream.filter(e -> e.action == MotionEvent.ACTION_MOVE);
        Observable<Event> pointer2Up = touchStream.filter(e -> e.action == MotionEvent.ACTION_POINTER_UP);
        Observable<Event> pointer1Up = touchStream.filter(e -> e.action == MotionEvent.ACTION_UP);
    }
複製程式碼

計算位移、旋轉和縮放

首先考慮單指拖動的操作流程:

手指按下 -> 手指移動 -> 手指抬起
複製程式碼

我們用兩次相鄰的手指移動的位移去移動圖片,計算方法如下:

Observable<Vector> delta1 = Observable.combineLatest(pointerMove, pointerMove.skip(1), (prev, cur) -> prev.p1.subtract(cur.p1));
複製程式碼

完整流程程式碼如下:

pointer1Down
        .flatMap(e -> delta1.takeUntil(pointer1Up))
        .subscribe(v -> {
            matrix.postTranslate(v.x, v.y);
            setImageMatrix(matrix);
        });
複製程式碼

再來考慮雙指操作流程:

第二個手指按下 -> 手指移動 -> 第二個手指抬起
複製程式碼

同樣,我們用兩次相鄰的手指移動計算圖片的位移、旋轉和縮放,定義類Delta儲存這些值

Observable<Delta> delta2 = Observable.combineLatest(pointerMove, pointerMove.skip(1), (prev, cur) -> {
    Delta delta = new Delta();
    delta.center = cur.center();
    delta.translate = prev.center().subtract(cur.center());
    delta.scale = prev.length() / cur.length();
    delta.rotate = cur.vector().angle(prev.vector());
    return delta;
});
複製程式碼

完整流程程式碼如下:

pointer2Down
        .flatMap(e -> delta2.takeUntil(pointer2Up))
        .subscribe(d -> {
            matrix.postTranslate(d.translate.x, d.translate.y);
            matrix.postRotate(d.rotate, d.center.x, d.center.y);
            matrix.postScale(d.scale, d.scale, d.center.x, d.center.y);
            setImageMatrix(matrix);
        });
複製程式碼

第二個手指按下的時候,單指拖動流程應該停止,第二個手指抬起的時候,單指拖動流程應該重新開始。所以我們需要修改單指拖動流程的實現:

pointer1Down
        .mergeWith(pointer2Up)
        .flatMap(e -> delta1.takeUntil(pointer1Up).takeUntil(pointer2Down))
        .subscribe(v -> {
            matrix.postTranslate(v.x, v.y);
            setImageMatrix(matrix);
        });
複製程式碼

完整程式碼見這裡

相關文章