3D版翻页公告效果
前言:
在逛小程序蘑菇街的时候,看到一个2D版滚动的翻页公告效果。其实看到这个效果的时候,一点都不觉得稀奇,因为之前也见过类似的。效果如下:
这里因为学习了3D平面的旋转,因此我自己也撸出了一个3D版的翻页公告效果:
是不是一下子觉得有趣多了呢,那就赶紧和我去看下如何做出这种效果吧 。
使用:
- 布局:
<!--指定从下到上翻滚-->
<com.xiangcheng.marquee3dview.Marquee3DView
android:id="@+id/marquee3DView"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:direction="D2U"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="#FFC0CB"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/marquee3DView"
app:layout_constraintStart_toStartOf="@+id/marquee3DView"
app:layout_constraintTop_toBottomOf="@+id/marquee3DView">
<!--从上到下翻滚-->
<com.xiangcheng.marquee3dview.Marquee3DView
android:id="@+id/marquee3DView2"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginStart="10dp"
app:back_color="#00ffffff"
app:direction="U2D"
app:highlight_color="#FF6347"
app:highlight_position="3"
app:rotate_duration="1500"
app:show_duration="1500" />
</LinearLayout>
- 属性:
<declare-styleable name="Marquee3DView">
<!--指定旋转的方向-->
<attr name="direction" format="enum">
<!--从上到下-->
<enum name="U2D" value="2" />
<!--从下到上-->
<enum name="D2U" value="1" />
</attr>
<!--高亮的item位置-->
<attr name="highlight_position" format="integer" />
<!--item的颜色-->
<attr name="back_color" format="color" />
<!--高亮的文字、下划线颜色-->
<attr name="highlight_color" format="color" />
<!--3D旋转的时间-->
<attr name="rotate_duration" format="integer" />
<!--停留显示的时间-->
<attr name="show_duration" format="integer" />
<!--右边文字的颜色-->
<attr name="label_text_color" format="color" />
<!--右边文字的大小-->
<attr name="label_text_size" format="dimension" />
<!--指定左边图片的半径-->
<attr name="label_bitmap_radius" format="dimension" />
<!--bitmap和text之间的间距-->
<attr name="label_bitmap_text_offset" format="dimension" />
</declare-styleable>
- 代码:
/**
* 设置显示的label
* @param marqueeLabels
*/
public void setMarqueeLabels(List<String> marqueeLabels)
/**
* 设置显示的bitmap
* @param labelBitmap
*/
public void setLabelBitmap(List<Bitmap> labelBitmap)
/**
* 点击监听
*
*/
setOnWhereItemClick(new Marquee3DView.OnWhereItemClick() {
@Override
public void onItemClick(int position) {
//TODO
}
});
- gradle:
compile 'com.xiangcheng:marquee3dlibs:1.0.1'
- maven:
<dependency>
<groupId>com.xiangcheng</groupId>
<artifactId>marquee3dlibs</artifactId>
<version>1.0.1</version>
<type>pom</type>
</dependency>
讲解:
- 初始化属性
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();
}
这里给出了两种情况旋转前、旋转后的示意图,上面的平行四边形都是一个平面,可以想象下。
其实讲解到这就基本没什么了,再就是一些细节性的代码了。如果有什么不明白的地方,可以互相交流。
总结:
(一):初始化一些需要的变量
(二):初始化动画变量
(三):绘制两个翻转的平面
项目文件目录截图:
项目目录结构
3D版翻页公告效果
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
3D版翻页公告效果的更多相关文章
- 一款基于css3的3D图片翻页切换特效
今天给大家分享一款基于css3的3D图片翻页切换特效.单击图片下方的滑块会切换上方的图片.动起你的鼠标试试吧,效果图如下: 在线预览 源码下载 实现的代码. html代码: <div id= ...
- 创意HTML5文字特效 类似翻页的效果
原文:创意HTML5文字特效 类似翻页的效果 之前在网上看到一款比较有新意的HTML5文字特效,文字效果是当鼠标滑过是出现翻开折叠的效果,类似书本翻页.于是我兴致勃勃的点开源码看了一下,发现其实实现也 ...
- jquery 实现智能炫酷的翻页相册效果
jquery 实现智能炫酷的翻页相册效果巧妙的运用 Html 的文档属性,大大减少jquery 的代码量,实现了智能炫酷的翻页相册.兼容性很好,实现了代码与标签的完全分离1. [代码]jquery ...
- Android 聊天表情输入、表情翻页带效果、下拉刷新聊天记录
经过一个星期的折腾,最终做完了这个Android 聊天表情输入.表情翻页带效果.下拉刷新聊天记录.这仅仅是一个单独聊天表情的输入,以及聊天的效果实现.由于我没有写server,所以没有两方聊天的效果. ...
- 15个最佳jQuery的翻页书效果的例子
在这里,你会发现15的jQuery的翻页书的插件,提供了良好的页面翻转的经验,并帮助创建类似书本的效果. jQuery的增添了一道亮丽的过渡到实际的页面在一本书或杂志HTML5. 1. BookBlo ...
- jquery css3问卷答题卡翻页动画效果
这个选项调查的特效以选项卡的形式,每答完一道题目自动切换到下一条,颇具特色.使用jQuery和CSS3,适合HTML5浏览器. 效果展示 http://hovertree.com/texiao/jqu ...
- jq小demo—图片翻页展示效果 animate()动画
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- jq demo—图片翻页展示效果 animate()动画
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- android开源新闻小程序、3D翻转公告效果、小说检索、Kotlin开发TODO清单等源码
Android精选源码 开源新闻小程序源码分享 android动态壁纸.锁屏动画.来电秀等源码 android笔记App效果源码 Android实现3D版翻页公告效果 android小说搜索阅读源码 ...
随机推荐
- VS MFC RADIO控件 选择
我们假设有两个RADIO控件:IDC_RADIO_SINGLE和IDC_RADIO_RANGE,我们的目的是默认选种IDC_RADIO_SINGLE控件. 方法一: CheckRadioButton( ...
- string那些事之replace
/* 用法一: 用str替换指定字符串从起始位置pos开始 长度为为len的字符串 string &replace(size_t pos, size_t len, const string&a ...
- 洛谷——P1755 斐波那契的拆分
P1755 斐波那契的拆分 题目背景 无 题目描述 已知任意一个正整数都可以拆分为若干个斐波纳契数,现在,让你求出n的拆分方法 输入输出格式 输入格式: 一个数t,表示有t组数据 接下来t行,每行一个 ...
- response.getWriter().write()与out.print()的区别(转)
1.首先介绍write()和print()方法的区别: (1).write():仅支持输出字符类型数据,字符.字符数组.字符串等 (2).print():可以将各种类型(包括Object)的数据通过默 ...
- 数值计算方法 | C语言实现几个数值计算方法(实验报告版)
目录 写在前面 实验一 牛顿插值方法的实现 实验二 龙贝格求积算法的实现 实验三 高斯列主元消去法的实现 实验四 最小二乘方法的实现 写在前面 使用教材:<数值计算方法>黄云清等编著 科学 ...
- iOS开发 Swift开发数独游戏(四) 游戏界面的界面与逻辑
一.游戏界面涉及到的功能点 1)数独格子的建模 (1)绘制数独格子要考虑到标记功能 所以要在每个格子内预先塞入9个标记数字,仅数独格子算下来就有9*9*9=729个格子且存在大量嵌套(这导致我在操作S ...
- [sharepoint]文档库,文件夹授权
写在前面 在项目中用到了文档库授权的方法,这里将查询到的方式总结一下. 涉及到的方法 在逻辑中用到的方法. /// <summary> /// 获取sharepoint站点角色定义 res ...
- tiny4412 串口驱动分析八 --- log打印的几个阶段之内核启动阶段(printk tiny4412串口驱动的注册)
作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...
- JsonDataObjects序列和还原
JsonDataObjects序列和还原 JsonDataObjects号称DELPHI最快的JSON库,且支持跨平台. // cxg 2017-9-12// Use JsonDataObjects( ...
- ios unit test 工程选择release时候报错Undefined symbols for architecture i386
Undefined symbols for architecture i386: "_OBJC_CLASS_$_ItemReturn", referenced from: objc ...