Android增強(qiáng)現(xiàn)實(shí)(二)-支持拖拽控制進(jìn)度和伸縮的VrGifView

1.Android增強(qiáng)現(xiàn)實(shí)(一)-AR的三種方式(展示篇)
2.Android增強(qiáng)現(xiàn)實(shí)(二)-支持拖拽控制進(jìn)度和伸縮的VrGifView
3.Android增強(qiáng)現(xiàn)實(shí)(三)-3D模型展示器

前言

前段時(shí)間研究了一下增強(qiáng)現(xiàn)實(shí)在Android端的實(shí)現(xiàn),目前大體分為兩種,全景立體圖(GIF和全景圖)和3D模型圖。這篇博客主要講一下關(guān)于GIF相關(guān)的實(shí)現(xiàn)方式。

效果

VrGifView

使用方式

1.Add it in your root build.gradle at the end of repositories:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Step 2. Add the dependency

dependencies {
       compile 'com.github.sdfdzx:VRShow:v1.0.2'
}

XML and Java

<com.study.xuan.gifshow.widget.VrGifView
        android:id="@+id/gif"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/demo"
        />


public class GifActivity extends AppCompatActivity {
    private VrGifView mGif;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gif);
        mGif = (VrGifView) findViewById(R.id.gif);
        mGif.setTouch(true);//是否 可觸摸
        mGif.setDrag(true);//是否可拖拽
        mGif.setScale(false);//是否可伸縮
        mGif.setMoveMode(VrGifView.MODE_FAST);//觸摸響應(yīng)速度
    }
}

技術(shù)分析

京東

大家應(yīng)該在淘寶和京東上看到過(guò)這樣的實(shí)現(xiàn)效果吧,我對(duì)他的分析是這樣的:

1.首先這是一個(gè)商品全景自動(dòng)旋轉(zhuǎn)的gif圖。
2.這個(gè)gif圖支持進(jìn)度的調(diào)整。(淘寶支持拖拽控制,京東支持陀螺儀傳感器控制)

基于以上的分析首先我們要實(shí)現(xiàn)的組件肯定要支持這幾個(gè)關(guān)鍵點(diǎn):
1.支持播放gif
2.支持gif的進(jìn)度控制(重要

這兩個(gè)條件其實(shí)就第一個(gè)來(lái)說(shuō)Glide就可以實(shí)現(xiàn),但是第二個(gè)條件就比較難了,其實(shí)gif就是圖片集的播放,想控制gif的進(jìn)度,目前Glide我還沒(méi)有找到相關(guān)的api可以控制(大家知道的話可以評(píng)論告訴我~),考慮到第二個(gè)條件,我最后選用了比較知名的Android端加載Gif的開(kāi)源庫(kù)android-gif-drawable,這個(gè)庫(kù)提供了對(duì)應(yīng)的api來(lái)對(duì)進(jìn)度進(jìn)行控制。

Animation control
GifDrawable implements an Animatable and MediaPlayerControl so you can use its methods and more:

stop() - stops the animation, can be called from any thread
start() - starts the animation, can be called from any thread
isRunning() - returns whether animation is currently running or not
reset() - rewinds the animation, does not restart stopped one
setSpeed(float factor) - sets new animation speed factor, eg. passing 2.0f will double the animation speed
seekTo(int position) - seeks animation (within current loop) to given position (in milliseconds)
getDuration() - returns duration of one loop of the animation
getCurrentPosition() - returns elapsed time from the beginning of a current loop of animation

目前兩個(gè)前提條件找到了,現(xiàn)在的問(wèn)題就是手勢(shì)控制進(jìn)度了,目前看起來(lái)一帆風(fēng)順沒(méi)有什么坑,繼續(xù)往下實(shí)現(xiàn)。

功能

既然前提條件已經(jīng)具備,現(xiàn)在就來(lái)提需求:

1.支持單指拖動(dòng)
2.支持雙指縮放
3.考慮一定的性能

實(shí)現(xiàn)關(guān)鍵點(diǎn)

一.單指拖動(dòng)
單指拖動(dòng)似乎很簡(jiǎn)單
實(shí)現(xiàn)思路:
1.獲取滑動(dòng)的距離
2.獲取Gif的總進(jìn)度,和MOVE時(shí)的當(dāng)前的進(jìn)度
3.滑動(dòng)距離/屏幕寬度 = MOVE時(shí)的當(dāng)前的進(jìn)度/Gif的總進(jìn)度,對(duì)應(yīng)將滑動(dòng)距離轉(zhuǎn)換成GIF的進(jìn)度,從而通過(guò)seekTo來(lái)控制GIF的進(jìn)度。

關(guān)鍵代碼:

private void rotateModel(MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                if (touchMode == TOUCH_NONE && event.getPointerCount() == 1) {
                    touchMode = TOUCH_DRAG;
                    gifDrawable.stop();
                    lastX = event.getX();
                    downTime = event.getDownTime();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (touchMode == TOUCH_DRAG) {
                    //通過(guò)move的時(shí)間控制刷新頻率
                    if ((event.getEventTime() - downTime) > moveSpeed) {
                        moveX = event.getX();
                        moveDis = moveX - lastX;
                        lastX = moveX;
                        curPos = gifDrawable.getCurrentPosition();
                        if ((curPos + moveDis * PX_TO_POS) < 0) {
                            curPos += moveDis * PX_TO_POS + gifLength;
                        } else {
                            curPos += moveDis * PX_TO_POS;
                        }
                        if (curPos < 0) {
                            curPos = 0;
                        }
                        gifDrawable.seekTo(curPos);
                        downTime = event.getEventTime();
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (touchMode == TOUCH_DRAG) {
                    touchMode = TOUCH_NONE;
                }
                gifDrawable.start();
                break;
        }
    }

思路濾清了,代碼還是很好理解的,這里唯一需要注意的地方:注意對(duì)于界面刷新頻率的控制,一開(kāi)始我沒(méi)有考慮這一點(diǎn),簡(jiǎn)單的只是移動(dòng)了就改變進(jìn)度,這時(shí)會(huì)發(fā)現(xiàn)在MOVE的過(guò)程中,gif會(huì)不停的閃黑屏,一開(kāi)始我以為是我計(jì)算進(jìn)度有問(wèn)題,怎么改都沒(méi)有解決,后來(lái)我看了下GifDrawable的源碼,每次都會(huì)重繪,我就懷疑是由于MOVE過(guò)程中的滑動(dòng)觸發(fā)頻率過(guò)快,導(dǎo)致刷新過(guò)快導(dǎo)致的,我便通過(guò)MOVE的時(shí)間來(lái)控制

if ((event.getEventTime() - downTime) > moveSpeed)

可以看到這里通過(guò)move時(shí)的時(shí)間點(diǎn)和down的時(shí)間點(diǎn)相減來(lái)控制觸發(fā)刷新的頻率,這里的moveSpeed是可以調(diào)整的。

/**
     * 設(shè)置觸摸觸發(fā)響應(yīng)速度
     */
    public void setMoveMode(int mode) {
        switch (mode) {
            case MODE_FAST:
                moveMode = MODE_FAST;
                moveSpeed = SPEED_FAST;
                break;
            case MODE_NORMAL:
                moveMode = MODE_NORMAL;
                moveSpeed = SPEED_NORMAL;
                break;
            case MODE_LOW:
                moveMode = MODE_LOW;
                moveSpeed = SPEED_LOW;
                break;
            default:
                moveMode = MODE_NORMAL;
                moveSpeed = SPEED_NORMAL;
                break;
        }
    }

二.雙指縮放
網(wǎng)上對(duì)于雙指縮放的做法很多,有通過(guò)矩陣變換,有通過(guò)canvas的,這里我考慮到原圖是一個(gè)GIF,對(duì)于雙指縮放,我選擇使用屬性動(dòng)畫(huà)來(lái)實(shí)現(xiàn)。
實(shí)現(xiàn)思路:
1.獲得雙指的距離
2.將距離轉(zhuǎn)換為scale的縮放量
3.利用ObjectAnimator來(lái)實(shí)現(xiàn)縮放。

關(guān)鍵代碼:

private void zoomScale(MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            // starts pinch
            case MotionEvent.ACTION_POINTER_DOWN:
                if (event.getPointerCount() >= 2) {
                    pinchStartDistance = getPinchDistance(event);
                    downTime = event.getDownTime();
                    if (pinchStartDistance > 50f) {
                        touchMode = TOUCH_ZOOM;
                    }
                }
                break;

            case MotionEvent.ACTION_MOVE:
                if (touchMode == TOUCH_ZOOM && pinchStartDistance > 0) {
                    // on pinch
                    if ((event.getEventTime() - downTime) > moveSpeed) {
                        if (getPinchDistance(event) > pinchStartDistance) {
                            //遞增
                            isUp = true;
                        } else {
                            isUp = false;
                        }
                        pinchScale = getPinchDistance(event) / pinchStartDistance;
                        if (checkScale(pinchScale)) {
                            changeScale(pinchScale);
                        }
                    }
                }
                break;

            // end pinch
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                pinchScale = 0;
                if (touchMode == TOUCH_ZOOM) {
                    touchMode = TOUCH_NONE;
                }
                break;
        }
    }

這里轉(zhuǎn)化其實(shí)和上面的原理相近,但是這里有同樣有幾個(gè)坑需要踩一下:
難點(diǎn):
1.刷新頻率
2.手指縮放誤差

和上面一樣,當(dāng)我一氣呵成實(shí)現(xiàn)后發(fā)現(xiàn)并沒(méi)有想象的那么簡(jiǎn)單,實(shí)現(xiàn)效果會(huì)發(fā)現(xiàn)當(dāng)我雙指放大的時(shí)候,GIF的大小總是有時(shí)候會(huì)莫名其妙的變小,我通過(guò)將縮放量LOG打出來(lái)發(fā)現(xiàn),雖然我們的雙指手勢(shì)是放大,但是在放大的過(guò)程中由于停頓等其他原因會(huì)有間歇性的變小的趨勢(shì),這樣GIF就會(huì)出現(xiàn)在變大的過(guò)程中變小,為了避免這樣的出現(xiàn),我的解決思路是這樣的:

1.過(guò)濾超小范圍的起始點(diǎn)
2.通過(guò)移動(dòng)趨勢(shì)判斷時(shí)變大還是變小
3.執(zhí)行動(dòng)畫(huà)之前判斷要執(zhí)行的動(dòng)畫(huà)是否符合當(dāng)前的變化趨勢(shì)。

case MotionEvent.ACTION_POINTER_DOWN:
                if (event.getPointerCount() >= 2) {
                    pinchStartDistance = getPinchDistance(event);
                    downTime = event.getDownTime();
                    //過(guò)濾超小范圍的起始點(diǎn)
                    if (pinchStartDistance > 50f) {
                        touchMode = TOUCH_ZOOM;
                    }
                }
                break;

可以看到我在down的時(shí)候?qū)τ诔》秶钠鹗键c(diǎn)是進(jìn)行了過(guò)濾的,只有大于50的才算雙指縮放。

case MotionEvent.ACTION_MOVE:
                if (touchMode == TOUCH_ZOOM && pinchStartDistance > 0) {
                    // on pinch
                    if ((event.getEventTime() - downTime) > moveSpeed) {
                        //判斷趨勢(shì)
                        if (getPinchDistance(event) > pinchStartDistance) {
                            //遞增
                            isUp = true;
                        } else {
                            isUp = false;
                        }
                        pinchScale = getPinchDistance(event) / pinchStartDistance;
                        //檢查變化是否符合趨勢(shì)
                        if (checkScale(pinchScale)) {
                            changeScale(pinchScale);
                        }
                    }
                }
                break;


private boolean checkScale(float pinchScale) {
        if (canAnim) {
            if (isUp) {
                if (pinchScale > 1) {
                    return true;
                }
            } else {
                if (pinchScale < 1) {
                    return true;
                }
            }
        }
        return false;
    }

可以看到,這里比較了和down的時(shí)候的距離變化,來(lái)判斷時(shí)變大還是變小,最后在執(zhí)行動(dòng)畫(huà)前先判斷一下當(dāng)前執(zhí)行的動(dòng)畫(huà)是否符合我們的移動(dòng)趨勢(shì),符合才執(zhí)行動(dòng)畫(huà),不符合不執(zhí)行。

總結(jié)

具體難點(diǎn)已經(jīng)分析完畢了,主要就是多思考一下,其實(shí)也沒(méi)有特別復(fù)雜的地方,只是在巨人的肩膀上封裝了一下,這里放上源碼地址

github地址:VRShow
喜歡的點(diǎn)個(gè)Star,謝謝~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容