从零开始打造一个 Android 3D立体旋转容器

github 代码下载地址 :https://github.com/ImmortalZ/StereoView

嗯,2个月没有写博客,是要好好反省下,趁着放暑假把这两个月看的东西好好沉淀下。嗯,就立下这个Flag,希望不要自己再打自己脸。

1.概述

回到正题,这次带来的效果,是一个Android 的3D立体旋转的效果。
当然灵感的来源,来自早些时间微博上看到的效果图。
非常酷有木有!作为程序猿我当然要把它加入我的下一个项目中啦!

原效果

这里写图片描述

我们实现的效果:

(为了更加可定制化,我在原图基础上新增了新的效果)

这里写图片描述

可以快速滚动,并且无限循环

这里写图片描述

这个是对一些参数的进行设定

这里写图片描述

对图片的包裹效果

这里写图片描述

因为本身继承自ViewGroup,所以基本控件都是可以包裹的

2.分析

因为代码量有点大,感觉把代码全部粘贴上来也不现实。所以想了解我的思路的盆友可以先来这里下载代码。然后边看代码边看我的分析

下载地址 :https://github.com/ImmortalZ/StereoView

通过我们实现的效果图可以发现:

1.切换的时候是一个3D立体的效果

2.布局中的每一个Item可以自由切换,且无限循环滚动

要解决上面的效果,我们需要什么技术点呢?

1.要想实现一个3D效果,我们可以借助Android中的Camera、Matrix

2.要想实现滚动,毫无疑问,我们需要借助Scroller

当然一切看起来很简单,其实不然,除此之外,你还需要对于滑动冲突进行处理等等,下面我开始介绍啦。

这就是我们这次项目的大致

20160715163613170

3.实现

因为我们是要打造一个容器类,所以肯定得继承自 ViewGroup 按照一般的思路,我们肯定是先要进行一些变量的申明,onMeasure,onLayout操作

完成这些操作后,我们需要在onTouchEvent中进行滑动事件的处理

3.1 完成无限循环滑动滚动

我们的item数量是有限的,如何实现无限循环滚动呢?很简单,以3个item为例子(分别为1,2,3),我们让屏幕显示的是2

如此反复,屏幕所在的位置始终是第2个item所在的位置,这样就实现了我们的无限循环滚动,向下滚动也是如此

20160715173427034

当手从屏幕上移开时,我们来看下这个方法changeByState(yVelocity);

20160715164957987

我们以mState = State.ToPre 为例子来说明

然后会进入addPre方法中

最后mScroller.startScroll(0, startY, 0, delta, duration); 开始执行。
执行的过程中会回调这个函数方法computeScroll

20160715172355404

完成到这一步,我们的无限滑动滚动就算是完成了

3.2 实现3D切换效果。

正常情况下,我们自定义ViewGroup并不需要重写dispatchDraw 方法。
而这里我们则需要重写

好,我们来drawScreen这个方法

这里面的关键就在于
mCamera.rotateX(degree);
mMatrix.preTranslate(-centerX, -centerY);
mMatrix.postTranslate(centerX, centerY);

对于Camera我们知道我们整个布局都是平铺的,为什么会产生3D的效果呢?原因就是这个Camera类,人如其名,它就相当于一个相机,它对物体进行拍照。我们把相机正对物体拍摄,拍摄出的效果就是平面的,当我们把相机旋转了90度再来拍摄原来物体,物体就相当于旋转了90度。
Camera拍摄完毕后,然后把拍摄的参数值传到Matrix中,Matrix再和Canvas绑定,由Canvas进行绘制。最终显示在屏幕中。

那么preTranslate,postTranslate又是怎么一回事呢?
很简单,我们知道坐标系是以(0,0)作为参照点的。现在我们对拍摄的对象进行的缩放变形操作是在物体的中心。我们需要把物体的中心先移动到(0,0)位置,最后再移动到物体原来中心位置即可。

具体的大家可以参考下这篇文章
http://blog.csdn.net/rav009/article/details/7763223 ( Android postTranslate和preTranslate的理解)

不过对于Camera的坐标系我还有一点点疑问,我准备有机会写一篇关于Camera和Matrix文章。

3.3 滑动事件冲突的处理(请先查看更新说明)

完成上面两个步骤,那么我们就算Over了吗?

不!还有很重要的一点,就是事件冲突的处理。 举个例子:我们把手放到我们的容器上,系统怎么知道我们这个滑动事件是给容器还是要给容器的子类的呢?

(给容器自己,则进行滑动的操作,给容器的子类,则容器的子类可以进行点击事件的判断处理)

对于这种情况,我就很大度啦,全部交给容器子类处理!子类不要,OK,那容器你自己拿来玩吧。

————之所以不走寻常路:交给容器处理,容器不需要再交给子类

原因在于:容器拿到滑动事件只需要做滑动操作,而子类则不同,它有点击事件需要判断,一个容器有很多子类,而很多子类只有一个共同的容器,如果把控制权交给容器,那么容器怎么可能能够判断得出不同的子类到底需不需要这个滑动事件呢?所以,既然这么麻烦,那么统统交给子类处理。

交给子类处理,则容器中onInterceptTouchEvent需要做如下操作

而子类(用CustomEdittext为例)的dispatchTouchEvent需要做如下判断

在isContain中,我做的是点击的坐标是否在Edittext中,在则拦截,子类处理,不在,则交给父类容器

当然交给子类这样也导致了一个问题,就是我如果需要给容器中的子类进行点击事件,则都需要自定义一个View(例如上面的CustomEdittext 继承自Edittext)。

例如我就自定义了三个View,不过还是很简单的,几分钟的事就搞定了(在自定义View中dispatchTouchEvent进行判断)。

具体的可以参考代码。

20160715180707016

更新说明 2016-8/5

滑动冲突之前我是把控制权交给了子类,这里https://github.com/Y-bao 这位作者提交的pull
request中将事件冲突交给了父类(StereoView)
,我这边通过了pull,我觉得写得挺好的,把点击事件的控制权转移给父类,就不需要自定义View。
如果你还想查看控制权转移给子类的代码(我之前的),可以点击这里

3.4 点击水纹波效果

细心的人会发现,我这里还有个RippleView。
没错这就是点击后有水纹波的效果。
Android本身可以在XML中用ripple实现,不过是Android 5.0以上,个人觉得兼容性不太好,就自己随便写了一个简易的,哈哈,效率不能保证,各位看客看看就好啦。

4.应用

4.1 定义的方法

使用方法也和其他的没有什么区别,我这里自定义了几个方法,我这里说明下。

自定义的方法

setStartScreen(int startScreen) :设置第一页展示的页面 @param startScreen (0,getChildCount-1)

setResistance(float resistance) : 设置滑动阻力 @param resistance (0,…)

setInterpolator(Interpolator mInterpolator) : 设置滚动时interpolator插补器

setAngle(float mAngle):设置滚动时两个item的夹角度数 [0f,180f]

setCan3D(boolean can3D) : 是否开启3D效果

setItem(int itemId) : 跳转到指定的item @param itemId [0,getChildCount-1]

toPre() : 上一页

toNext() : 下一页

定义的回调接口

 

20160715181619444

4.2 使用方法

直接在布局中

20160715182509446

在代码中

20160715182602963

4.3 缺陷说明

目前容器的item数量需要大于等于3,小于3个滑动时会些问题。设置的最开始展示的item位置不能是第一个或者最后一个,这么做是为了保证第1个或者最后一个被隐藏,从而保证最开始向上滑动或者向下滑动时的正常。

5.下载

如果觉得对你有帮助,欢迎 star,fork,如果对于我感兴趣,欢迎follow 我

下载地址 :https://github.com/ImmortalZ/StereoView

参考文章:

http://blog.csdn.net/dawanganban/article/details/38421221

http://blog.csdn.net/rav009/article/details/7763223

1 2 收藏 2 评论

关于作者:ImmortalZ

简介还没来得及写 :) 个人主页 · 我的文章 · 1 ·   

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部