Android增强现实(二)-支持拖拽控制进度和伸缩的VrGifView

1.Android增强现实(一)-AR的三种方式(展示篇)
2.Android增强现实(二)-支持拖拽控制进度和伸缩的VrGifView
3.Android增强现实(三)-3D模型展示器

前言

前段时间研究了一下增强现实在Android端的实现,目前大体分为两种,全景立体图(GIF和全景图)和3D模型图。这篇博客主要讲一下关于GIF相关的实现方式。

效果

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);//触摸响应速度
    }
}

技术分析

京东

大家应该在淘宝和京东上看到过这样的实现效果吧,我对他的分析是这样的:

1.首先这是一个商品全景自动旋转的gif图。
2.这个gif图支持进度的调整。(淘宝支持拖拽控制,京东支持陀螺仪传感器控制)

基于以上的分析首先我们要实现的组件肯定要支持这几个关键点:
1.支持播放gif
2.支持gif的进度控制(重要

这两个条件其实就第一个来说Glide就可以实现,但是第二个条件就比较难了,其实gif就是图片集的播放,想控制gif的进度,目前Glide我还没有找到相关的api可以控制(大家知道的话可以评论告诉我~),考虑到第二个条件,我最后选用了比较知名的Android端加载Gif的开源库android-gif-drawable,这个库提供了对应的api来对进度进行控制。

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

目前两个前提条件找到了,现在的问题就是手势控制进度了,目前看起来一帆风顺没有什么坑,继续往下实现。

功能

既然前提条件已经具备,现在就来提需求:

1.支持单指拖动
2.支持双指缩放
3.考虑一定的性能

实现关键点

一.单指拖动
单指拖动似乎很简单
实现思路:
1.获取滑动的距离
2.获取Gif的总进度,和MOVE时的当前的进度
3.滑动距离/屏幕宽度 = MOVE时的当前的进度/Gif的总进度,对应将滑动距离转换成GIF的进度,从而通过seekTo来控制GIF的进度。

关键代码:

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) {
                    //通过move的时间控制刷新频率
                    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;
        }
    }

思路滤清了,代码还是很好理解的,这里唯一需要注意的地方:注意对于界面刷新频率的控制,一开始我没有考虑这一点,简单的只是移动了就改变进度,这时会发现在MOVE的过程中,gif会不停的闪黑屏,一开始我以为是我计算进度有问题,怎么改都没有解决,后来我看了下GifDrawable的源码,每次都会重绘,我就怀疑是由于MOVE过程中的滑动触发频率过快,导致刷新过快导致的,我便通过MOVE的时间来控制

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

可以看到这里通过move时的时间点和down的时间点相减来控制触发刷新的频率,这里的moveSpeed是可以调整的。

/**
     * 设置触摸触发响应速度
     */
    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;
        }
    }

二.双指缩放
网上对于双指缩放的做法很多,有通过矩阵变换,有通过canvas的,这里我考虑到原图是一个GIF,对于双指缩放,我选择使用属性动画来实现。
实现思路:
1.获得双指的距离
2.将距离转换为scale的缩放量
3.利用ObjectAnimator来实现缩放。

关键代码:

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;
        }
    }

这里转化其实和上面的原理相近,但是这里有同样有几个坑需要踩一下:
难点:
1.刷新频率
2.手指缩放误差

和上面一样,当我一气呵成实现后发现并没有想象的那么简单,实现效果会发现当我双指放大的时候,GIF的大小总是有时候会莫名其妙的变小,我通过将缩放量LOG打出来发现,虽然我们的双指手势是放大,但是在放大的过程中由于停顿等其他原因会有间歇性的变小的趋势,这样GIF就会出现在变大的过程中变小,为了避免这样的出现,我的解决思路是这样的:

1.过滤超小范围的起始点
2.通过移动趋势判断时变大还是变小
3.执行动画之前判断要执行的动画是否符合当前的变化趋势。

case MotionEvent.ACTION_POINTER_DOWN:
                if (event.getPointerCount() >= 2) {
                    pinchStartDistance = getPinchDistance(event);
                    downTime = event.getDownTime();
                    //过滤超小范围的起始点
                    if (pinchStartDistance > 50f) {
                        touchMode = TOUCH_ZOOM;
                    }
                }
                break;

可以看到我在down的时候对于超小范围的起始点是进行了过滤的,只有大于50的才算双指缩放。

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;


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

可以看到,这里比较了和down的时候的距离变化,来判断时变大还是变小,最后在执行动画前先判断一下当前执行的动画是否符合我们的移动趋势,符合才执行动画,不符合不执行。

总结

具体难点已经分析完毕了,主要就是多思考一下,其实也没有特别复杂的地方,只是在巨人的肩膀上封装了一下,这里放上源码地址

github地址:VRShow
喜欢的点个Star,谢谢~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342