你的浏览器禁用了JavaScript, 请开启后刷新浏览器获得更好的体验!
首页
热门
推荐
精选
登录
|
注册
3D版翻页公告效果
立即下载
用AI写一个
该例子支持:好用才打赏哦
现在下载学习
发布时间:2018-04-14
4人
|
浏览:2611次
|
收藏
|
分享
技术:android camera、matrix、属性动画
运行环境:android studio+android sdk25
概述
通过camera、matrix、属性动画实现的一款3D版公告效果
详细
###前言: 在逛小程序**蘑菇街**的时候,看到一个**2D**版滚动的翻页公告效果。其实看到这个效果的时候,一点都不觉得稀奇,因为之前也见过类似的。效果如下: ![蘑菇街效果.gif](/contentImages/image/jianshu/2528336-0371ffd903b1602d.gif) 这里因为学习了**3D**平面的旋转,因此我自己也撸出了一个**3D**版的翻页公告效果: ![simple.gif](/contentImages/image/jianshu/2528336-869e723e971b8177.gif) 是不是一下子觉得有趣多了呢,那就赶紧和我去看下如何做出这种效果吧 。 ###使用: - **布局:** ```
``` - **属性:** ```
``` - **代码:** ``` /** * 设置显示的label * @param marqueeLabels */ public void setMarqueeLabels(List
marqueeLabels) ``` ``` /** * 设置显示的bitmap * @param labelBitmap */ public void setLabelBitmap(List
labelBitmap) ``` ``` /** * 点击监听 * */ setOnWhereItemClick(new Marquee3DView.OnWhereItemClick() { @Override public void onItemClick(int position) { //TODO } }); ``` - **gradle:** ``` compile 'com.xiangcheng:marquee3dlibs:1.0.1' ``` - **maven:** ```
com.xiangcheng
marquee3dlibs
1.0.1
pom
``` ###讲解: - **初始化属性** ``` private void initArgus(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Marquee3DView); backColor = typedArray.getColor(R.styleable.Marquee3DView_back_color, Color.parseColor("#cccccc")); direction = typedArray.getInt(R.styleable.Marquee3DView_direction, D2U); highLightPosition = typedArray.getInt(R.styleable.Marquee3DView_highlight_position, highLightPosition); highLightColor = typedArray.getColor(R.styleable.Marquee3DView_highlight_color, Color.parseColor("#FF1493")); rotateDuration = typedArray.getInt(R.styleable.Marquee3DView_rotate_duration, rotateDuration); showDuration = typedArray.getInt(R.styleable.Marquee3DView_show_duration, showDuration); labelColor = typedArray.getColor(R.styleable.Marquee3DView_label_text_color, Color.parseColor("#778899")); labelTextSize = (int) typedArray.getDimension(R.styleable.Marquee3DView_label_text_size, sp2px(15)); labelBitmapRadius = (int) typedArray.getDimension(R.styleable.Marquee3DView_label_bitmap_radius, dp2px(10)); labelBitmapTextOffset = (int) typedArray.getDimension(R.styleable.Marquee3DView_label_bitmap_text_offset, dp2px(10)); } ``` - **初始化变量** ``` private void initialize() { camera = new Camera(); matrix = new Matrix(); textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textPaint.setTextSize(labelTextSize); textPaint.setColor(labelColor); currentPaint = new Paint(Paint.ANTI_ALIAS_FLAG); currentPaint.setTextSize(labelTextSize); currentPaint.setColor(labelColor); nextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); nextPaint.setTextSize(labelTextSize); nextPaint.setColor(labelColor); linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); linePaint.setColor(highLightColor); linePaint.setStrokeWidth(dp2px(1)); linePaint.setStyle(Paint.Style.FILL); highLightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); highLightPaint.setTextSize(sp2px(15)); highLightPaint.setColor(highLightColor); textRegion = new Region(); mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBitmapPaint.setColor(Color.WHITE); mBitmapPaint.setStrokeWidth(0); } ``` - **初始化动画** ``` private void initAnimation() { showItemRunable = new ShowItemRunable(); //角度变化是0到90度的区间 rotateAnimator = ValueAnimator.ofFloat(0, 90); rotateAnimator.setDuration(rotateDuration); rotateAnimator.setInterpolator(new LinearInterpolator()); rotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { isRunning = true; //当前变化的角度变量,在绘制的时候使用 changeRotate = (float) animation.getAnimatedValue(); //计算当前的画笔的透明度(从255到0的过程) caculateCurrentPaint(changeRotate); //计算下一个item的画笔透明度(从0到255的过程) caculateNextPaint(changeRotate); //从0到height的一个过程,这里因为旋转的时候,同时还要进行平移 translateY = height * animation.getAnimatedFraction(); invalidate(); } }); //处理旋转结束后,停留一会显示 rotateAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); isRunning = false; postDelayed(showItemRunable, showDuration); } }); //刚进来的时候,在第一个item上进行停留 startRunable = new StartRunable(); postDelayed(startRunable, showDuration); } //停留显示完的操作 private class ShowItemRunable implements Runnable { @Override public void run() { currentItem++; if (currentItem >= marqueeLabels.size()) { currentItem = 0; } rotateAnimator.start(); } } //刚进来时第一个item显示完后的操作 private class StartRunable implements Runnable { @Override public void run() { hasStart = true; rotateAnimator.start(); } } //当前画笔透明度的改变(255——>0) private void caculateCurrentPaint(float rotateAngle) { float percent = rotateAngle / 90; int alpha = (int) (255 - percent * 255); currentPaint.setAlpha(alpha); } //下一个item的画笔透明度的改变(0——>255) private void caculateNextPaint(float rotateAngle) { float percent = rotateAngle / 90; int alpha = (int) (percent * 255); nextPaint.setAlpha(alpha); } ``` 上面动画部分,其实你要关心的就是两个变量:`changeRotate`**(0——>90度变化)**、`translateY`**(0——>height变化)** - **绘制** ``` @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (marqueeLabels == null || marqueeLabels.size() <= 0) { return; } drawCurrentItem(canvas); drawNextItem(canvas); } ``` ``` private void drawCurrentItem(Canvas canvas) { canvas.save(); camera.save(); if (direction == D2U) { //当前的item从下到上转动,逆时针旋转,角度是增大的过程 camera.rotateX(changeRotate); } else { //从上到下旋转,顺时针旋转,角度是负角 camera.rotateX(-changeRotate); } camera.getMatrix(matrix); camera.restore(); if (direction == D2U) { 将旋转中心至为下面一条边的中点上 matrix.preTranslate(-width / 2, -height); //这里由于当前的item是往上转动的,下面的一条边最后是在0的位置了 matrix.postTranslate(width / 2, height - translateY); } else { //这里如果是往下转动时,旋转中心就是上面一条边的中点了 matrix.preTranslate(-width / 2, 0); //往下转动时,上面的边是不断地往下移动的,因此y轴是增大的 matrix.postTranslate(width / 2, translateY); } //创建绘制的内容 textBitmap = createChild(currentItem, false); canvas.drawBitmap(textBitmap, matrix, null); canvas.restore(); } ``` ``` //这里用到了隔离绘制,将最后要画的东西都放到了bitmap上 private Bitmap createChild(int position, boolean isNext) { Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); canvas.drawColor(backColor); if (labelBitmap != null && labelBitmap.size() > 0) { //绘制bitmap drawLabelBitmap(canvas, position); } Paint.FontMetrics fontMetrics = textPaint.getFontMetrics(); float allHeight = fontMetrics.descent - fontMetrics.ascent; float textWidth = textPaint.measureText(marqueeLabels.get(position)); Rect rect = new Rect(); rect.left = (int) labelTextStart; rect.right = (int) (labelTextStart + textWidth); rect.top = (int) (height / 2 - allHeight / 2); rect.bottom = (int) (height / 2 + allHeight / 2); textRegion.set(rect); //这里分是不是绘制下一个item if (isNext) { //如果是高亮的item,需要绘制下划线,以及改为高亮画笔 if (highLightPosition == position) { caculateHighLightPaint(changeRotate, true); canvas.drawText(marqueeLabels.get(position), labelTextStart, height / 2 - allHeight / 2 - fontMetrics.ascent, highLightPaint); canvas.drawLine(labelTextStart, (float) (height * 1.0 / 2 + allHeight / 2), labelTextStart + textWidth, (float) (height * 1.0 / 2 + allHeight / 2), linePaint); } else { canvas.drawText(marqueeLabels.get(position), labelTextStart, height / 2 - allHeight / 2 - fontMetrics.ascent, nextPaint); } } else { if (highLightPosition == position) { caculateHighLightPaint(changeRotate, false); canvas.drawText(marqueeLabels.get(position), labelTextStart, height / 2 - allHeight / 2 - fontMetrics.ascent, highLightPaint); canvas.drawLine(labelTextStart, (float) (height * 1.0 / 2 + allHeight / 2), labelTextStart + textWidth, (float) (height * 1.0 / 2 + allHeight / 2), linePaint); } else { canvas.drawText(marqueeLabels.get(position), labelTextStart, height / 2 - allHeight / 2 - fontMetrics.ascent, currentPaint); } } return bitmap; } ``` ``` //绘制左边的bitmap private void drawLabelBitmap(Canvas canvas, int position) { int layer = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG); //先画圆,dst层 canvas.drawCircle(labelBitmapRadius, height / 2, labelBitmapRadius, mBitmapPaint); //该mode下取两部分的交集部分 mBitmapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); //src层 canvas.drawBitmap(labelBitmap.get(position), 0, height / 2 - labelBitmapRadius, mBitmapPaint); mBitmapPaint.setXfermode(null); canvas.restoreToCount(layer); labelTextStart = labelBitmapRadius * 2 + labelBitmapTextOffset; } ``` ``` //计算高亮的画笔的透明度,跟普通的画笔一样的算法 private void caculateHighLightPaint(float rotate, boolean isNext) { if (isNext) { float percent = rotate / 90; int alpha = (int) (percent * 255); highLightPaint.setAlpha(alpha); linePaint.setAlpha(alpha); } else { float percent = rotate / 90; int alpha = (int) (255 - percent * 255); highLightPaint.setAlpha(alpha); linePaint.setAlpha(alpha); } } ``` ``` private void drawNextItem(Canvas canvas) { caculateNextItem(); canvas.save(); camera.save(); if (direction == D2U) { //从下到上时,另外一个面初始位置是-90度,最后趋于0度位置 camera.rotateX(-90 + changeRotate); } else { //从上到下是90度到0度的过程 camera.rotateX(90 - changeRotate); } camera.getMatrix(matrix); camera.restore(); if (direction == D2U) { //从下到上,旋转点是上面一条边的中点 matrix.preTranslate(-width / 2, 0); //初始位置是height,最后到了0的位置 matrix.postTranslate(width / 2, height + (-translateY)); } else { //从上到下,旋转点是下面一条边的中点 matrix.preTranslate(-width / 2, -height); //初始位置是0,最后到了height位置 matrix.postTranslate(width / 2, translateY); } textBitmap = createChild(nextItem, true); canvas.drawBitmap(textBitmap, matrix, null); canvas.restore(); } ``` ![从上到下旋转示意图.png](/contentImages/image/jianshu/2528336-a12cf06ed32f2dba.png) ![从下到上旋转示意图.png](/contentImages/image/jianshu/2528336-bd327cf5842e36f0.png) 这里给出了两种情况**旋转前**、**旋转后**的示意图,上面的平行四边形都是一个平面,可以想象下。 其实讲解到这就基本没什么了,再就是一些细节性的代码了。如果有什么不明白的地方,可以互相交流。 ###总结: (一):初始化一些需要的变量 (二):初始化动画变量 (三):绘制两个翻转的平面 ###项目文件目录截图: > 项目目录结构 ![](/contentImages/image/20180413/3jPPbRxFdafdcgOaT2e.jpg)
本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢
感谢
0
手机上随时阅读、收藏该文章 ?请扫下方二维码
相似例子推荐
评论
作者
随风者
购买服务
购买服务
服务描述:
demo不懂的地方可以讲给你听
服务价格:
¥10
我要联系
6
例子数量
107
帮助
22
感谢
评分详细
可运行:
4.5
分
代码质量:
4.5
分
文章描述详细:
4.5
分
代码注释:
4.5
分
综合:
4.5
分
作者例子
苹果版小黄车(ofo)app主页菜单效果
一分钟搞定触手app主页酷炫滑动切换效果
3D版翻页公告效果
定制一个类似地址选择器的view
仿360手机助手下载按钮
Mvp快速搭建商城购物车模块