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

我们实现的效果:

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

这里写图片描述

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

这里写图片描述

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

这里写图片描述

对图片的包裹效果

这里写图片描述

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

2.分析


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

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

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

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

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

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

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

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

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

这里写图片描述

3.实现


因为我们是要打造一个容器类,所以肯定得继承自 ViewGroup

按照一般的思路,我们肯定是先要进行一些变量的申明,onMeasure,onLayout操作

private void init(Context context) {

mCamera = new Camera();

mMatrix = new Matrix();

if (mScroller == null) {

mScroller = new Scroller(context);

}

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

measureChildren(widthMeasureSpec, heightMeasureSpec);

mWidth = getMeasuredWidth();

mHeight = getMeasuredHeight();

//滑动到设置的StartScreen位置

scrollTo(0, mStartScreen * mHeight);

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

int childTop = 0;

for (int i = 0; i < getChildCount(); i++) {

View child = getChildAt(i);

if (child.getVisibility() != GONE) {

child.layout(0, childTop,

child.getMeasuredWidth(), childTop + child.getMeasuredHeight());

childTop = childTop + child.getMeasuredHeight();

}

}

}

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

3.1 完成无限循环滑动滚动

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

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

这里写图片描述

@Override

public boolean onTouchEvent(MotionEvent event) {

if (mVelocityTracker == null) {

mVelocityTracker = VelocityTracker.obtain();

}

mVelocityTracker.addMovement(event);

float y = event.getY();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

if (!mScroller.isFinished()) {

//当上一次滑动没有结束时,再次点击,强制滑动在点击位置结束

mScroller.setFinalY(mScroller.getCurrY());

mScroller.abortAnimation();

scrollTo(0, getScrollY());

}

mDownY = y;

break;

case MotionEvent.ACTION_MOVE:

int realDelta = (int) (mDownY - y);

mDownY = y;

if (mScroller.isFinished()) {

//因为要循环滚动

recycleMove(realDelta);

}

break;

case MotionEvent.ACTION_UP:

mVelocityTracker.computeCurrentVelocity(1000);

float yVelocity = mVelocityTracker.getYVelocity();

//滑动的速度大于规定的速度,或者向上滑动时,上一页页面展现出的高度超过1/2。则设定状态为State.ToPre

if (yVelocity > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)) {

mState = State.ToPre;

} else if (yVelocity < -standerSpeed || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)) {

//滑动的速度大于规定的速度,或者向下滑动时,下一页页面展现出的高度超过1/2。则设定状态为State.ToNext

mState = State.ToNext;

} else {

mState = State.Normal;

}

//根据mState进行相应的变化

changeByState(yVelocity);

if (mVelocityTracker != null) {

mVelocityTracker.recycle();

mVelocityTracker = null;

}

break;

}

//返回true,消耗点击事件

return true;

}

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

这里写图片描述

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

/**

  • mState = State.ToPre 时进行的动作

  • @param yVelocity 竖直方向的速度

*/

private void toPreAction(float yVelocity) {

int startY;

int delta;

int duration;

mState = State.ToPre;

addPre();//增加新的页面

//计算松手后滑动的item个数

int flingSpeedCount= (yVelocity - standerSpeed) > 0 ? (int) (yVelocity - standerSpeed) : 0;

addCount = flingSpeedCount/ flingSpeed + 1;

//mScroller开始的坐标

startY = getScrollY() + mHeight;

setScrollY(startY);

//mScroller 移动的距离

delta = -(startY - mStartScreen * mHeight) - (addCount - 1) * mHeight;

duration = (Math.abs(delta)) * 3;

mScroller.startScroll(0, startY, 0, delta, duration);

addCount–;

}

然后会进入addPre方法中

/**

  • 把最后一个item移动到第一个item位置

*/

private void addPre() {

mCurScreen = ((mCurScreen - 1) + getChildCount()) % getChildCount();

int childCount = getChildCount();

View view = getChildAt(childCount - 1);

removeViewAt(childCount - 1);

addView(view, 0);

if (iStereoListener != null) {

iStereoListener.toPre(mCurScreen);

}

}

最后mScroller.startScroll(0, startY, 0, delta, duration); 开始执行。

执行的过程中会回调这个函数方法computeScroll

这里写图片描述

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

3.2 实现3D切换效果。

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

而这里我们则需要重写

@Override

protected void dispatchDraw(Canvas canvas) {

if (!isAdding && isCan3D) {

//当开启3D效果并且当前状态不属于 computeScroll中 addPre() 或者addNext()

//如果不做这个判断,addPre() 或者addNext()时页面会进行闪动一下

//我当时写的时候就被这个坑了,后来通过log判断,原来是computeScroll中的onlayout,和子Child的draw触发的顺序导致的。

//知道原理的朋友希望可以告知下

for (int i = 0; i < getChildCount(); i++) {

drawScreen(canvas, i, getDrawingTime());

}

} else {

isAdding = false;

super.dispatchDraw(canvas);

}

}

好,我们来drawScreen这个方法

private void drawScreen(Canvas canvas, int i, long drawingTime) {

int curScreenY = mHeight * i;

//屏幕中不显示的部分不进行绘制

if (getScrollY() + mHeight < curScreenY) {

return;

}

if (curScreenY < getScrollY() - mHeight) {

return;

}

float centerX = mWidth / 2;

float centerY = (getScrollY() > curScreenY) ? curScreenY + mHeight : curScreenY;

float degree = mAngle * (getScrollY() - curScreenY) / mHeight;

if (degree > 90 || degree < -90) {

return;

}

canvas.save();

mCamera.save();

mCamera.rotateX(degree);

mCamera.getMatrix(mMatrix);

mCamera.restore();

mMatrix.preTranslate(-centerX, -centerY);

mMatrix.postTranslate(centerX, centerY);

canvas.concat(mMatrix);

drawChild(canvas, getChildAt(i), drawingTime);

canvas.restore();

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。

不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

image

大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《2017-2021字节跳动Android面试历年真题解析》

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

-1711765657066)]

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

[外链图片转存中…(img-y25GB202-1711765657066)]

大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-55SVw0Fj-1711765657067)]

《2017-2021字节跳动Android面试历年真题解析》

[外链图片转存中…(img-WqvuizyW-1711765657067)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
随着智能手机移动嵌入式平台硬件性能的不断提升,3D游戏应用也逐渐普及开来。《Android 3D游戏开发技术宝典:OpenGL ES 2.0》结合作者多年从事3D游戏应用开发的宝贵经验,全面介绍了与Android平台相关的必知必会的基础知识及大型完整3D案例,讲解上由浅入深,循序渐进,起点低、终点高,既适合初学者学习,也适合有一定基础的读者进一步提升之用。另外,由于OpenGL ES 2.0的着色语言通用于各种移动嵌入式平台,因此,《Android 3D游戏开发技术宝典:OpenGL ES 2.0》中与着色器开发相关的60%左右的内容还可供iPhone、Windows Mobile、MeeGoo等平台的开发人员参考。 全书共22章,其中第1章与第2章为Android平台相关的一些基础知识;第3章~第10章介绍了基于OpenGL ES 2.0进行3D应用开发的一些必知必会的基本知识;第11章~第15章介绍了一些高级特效的实现方法;第16章~第17章介绍了3D游戏开发中相关的一些物理、碰撞检测知识以及常用的3D物理引擎JBullet;第19章介绍了3种人机交互的高级技术;第20章~第22章给出了3个完整的大型3D游戏案例,总代码量接近6万行。同时为了便于读者的学习,《Android 3D游戏开发技术宝典:OpenGL ES 2.0》附赠的光盘中包含了书中所有案例的完整源代码,同时给出了最后3个完整大型3D游戏案例的讲解视频,最大限度地帮助读者快速掌握相应的开发技术。 《Android 3D游戏开发技术宝典:OpenGL ES 2.0》适合Android程序员、游戏开发者及Android爱好者学习,也可以作为相关培训学校和大专院校相关专业的教学用书。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值