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)方式。
效果

使用方式
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,謝謝~