自定义UI 使用Camera做三维变换
Posted Notzuonotdied
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义UI 使用Camera做三维变换相关的知识,希望对你有一定的参考价值。
系列文章目录
文章目录
前言
这系列的文章主要是基于扔物线的HenCoderPlus课程的源码来分析学习。
- 扔物线课程源码:CameraView.java
- android官方文档:自定义绘制
这一篇文章主要介绍的是Camera做三维变换,更多细节请见:HenCoder Android 开发进阶:自定义 View 1-4 Canvas 对绘制的辅助。
创建绘制对象
我们需要创建一个画笔🖌Paint
来绘制我们的头像的边框,也需要提前加载我们的头像到内存(Bitmap)中。
public class CameraView extends View {
// 抗锯齿
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CameraView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
}
加载图片
为避免出现
java.lang.OutOfMemory
异常,请先检查位图的尺寸,然后再对其进行解码,除非您绝对信任该来源可为您提供大小可预测的图片数据,以轻松适应可用的内存。——引用自Android官方文档:高效加载大型位图
下述源码的分析请见官方的说明:将按比例缩小的版本加载到内存中。
public class CameraView extends View {
// 图片大小
private static final int IMAGE_WIDTH = 600;
// 头像的Bitmap
Bitmap bitmap;
public CameraView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
{
bitmap = Utils.getAvatar(getResources(), IMAGE_WIDTH);
}
}
public class Utils {
public static Bitmap getAvatar(Resources res, int width) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, R.drawable.avatar_rengwuxian, options);
options.inJustDecodeBounds = false;
options.inDensity = options.outWidth;
options.inTargetDensity = width;
return BitmapFactory.decodeResource(res, R.drawable.avatar_rengwuxian, options);
}
}
自定义绘制内容
先上一下我们使用Camera做三维变换的效果图。
定义绘制的位置
public class CameraView extends View {
// 图片大小
private static final int IMAGE_WIDTH = 600;
// 图片绘制的偏移量(left、top)
private static final int BITMAP_OFFSET = 200;
public CameraView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
}
变换流程解析
Canvas
的几何变换顺序是反的,所以 Canvas
的位置变换 translate
、图形旋转 rotate
等都是反着写的。
变换参数
public class CameraView extends View {
// 抗锯齿
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 相机
Camera camera = new Camera();
// 图片大小
private static final int IMAGE_WIDTH = 600;
// 图片的大小的一半
private static final int HALF_OF_IMAGE_WIDTH = IMAGE_WIDTH / 2;
// 图片绘制的偏移量(left、top)
private static final int BITMAP_OFFSET = 200;
// Canvas移动到圆点的偏移量(dx、dy)
private static final int OFFSET = HALF_OF_IMAGE_WIDTH + BITMAP_OFFSET;
// Canvas旋转的角度
private static final int ROTATE_ANGLE = 45;
}
先放上两部分单独的绘制效果:
左上部分变换
public class CameraView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制上半部分
canvas.save();
// ... 省略 ...
// 1. 将图片的中心移动到视图的圆点位置
canvas.translate(-OFFSET, -OFFSET);
canvas.drawBitmap(bitmap, BITMAP_OFFSET, BITMAP_OFFSET, paint);
canvas.restore();
}
}
3. 裁剪图像,保留x轴上方的灰色区域
public class CameraView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制上半部分
canvas.save();
// ... 省略 ...
// 3. 裁剪图像,保留x轴上方的灰色区域
canvas.clipRect(-IMAGE_WIDTH, -IMAGE_WIDTH, IMAGE_WIDTH, 0);
// 2. 图像以视图圆点为中心,旋转45°
canvas.rotate(ROTATE_ANGLE);
// 1. 将图片的中心移动到视图的圆点位置
canvas.translate(-OFFSET, -OFFSET);
canvas.drawBitmap(bitmap, BITMAP_OFFSET, BITMAP_OFFSET, paint);
canvas.restore();
}
}
这里简单解释下 canvas.clipRect
入参采用的是 IMAGE_WIDTH
的原因:作者懒 计算方便……╮(╯▽╰)╭
设 IMAGE_WIDTH
为
W
W
W,那么可以得出以下几个坐标位置:
坐标点 | 坐标位置 | 对应点 | 坐标位置 |
---|---|---|---|
A | ( − 1 2 W 2 -\\sqrt{\\frac{1}{2}W^2} −21W2, 0 0 0) | left | ( − W -W −W, 0 0 0) |
B | ( 0 0 0, 1 2 W 2 \\sqrt{\\frac{1}{2}W^2} 21W2) | top | ( 0 0 0, W W W) |
C | ( 1 2 W 2 \\sqrt{\\frac{1}{2}W^2} 21W2, 0 0 0) | right | ( W W W, 0 0 0) |
D | ( 0 0 0, − 1 2 W 2 -\\sqrt{\\frac{1}{2}W^2} −21W2) | bottom | ( 0 0 0, − W -W −W) |
由上,易得: W > 1 2 W 2 W > \\sqrt{\\frac{1}{2}W^2} W>21W2
5. 将图像中心从视图圆点恢复到原来位置
public class CameraView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制上半部分
canvas.save();
// 5. 将图像中心从视图圆点恢复到原来位置
canvas.translate(OFFSET, OFFSET);
// 4. 图像以视图圆点为中心,旋转-45°
canvas.rotate(-ROTATE_ANGLE);
// 3. 裁剪图像,保留x轴上方的灰色区域
canvas.clipRect(-IMAGE_WIDTH, -IMAGE_WIDTH, IMAGE_WIDTH, 0);
// 2. 图像以视图圆点为中心,旋转45°
canvas.rotate(ROTATE_ANGLE);
// 1. 将图片的中心移动到视图的圆点位置
canvas.translate(-OFFSET, -OFFSET);
canvas.drawBitmap(bitmap, BITMAP_OFFSET, BITMAP_OFFSET, paint);
canvas.restore();
}
}
右下部分变换
右下部分基本和上一章节一致,这里仅仅介绍不一样的地方。
3. 裁剪图像,保留x轴下方的灰色区域
public class CameraView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制上半部分
canvas.save();
// ... 省略 ...
// 3. 裁剪图像,保留x轴上方的灰色区域
canvas.clipRect(-IMAGE_WIDTH, 0, IMAGE_WIDTH, IMAGE_WIDTH);
// 2. 图像以视图圆点为中心,旋转45°
canvas.rotate(ROTATE_ANGLE);
// 1. 将图片的中心移动到视图的圆点位置
canvas.translate(-OFFSET, -OFFSET);
canvas.drawBitmap(bitmap, BITMAP_OFFSET, BITMAP_OFFSET, paint);
canvas.restore();
}
}
3D变换
这里有一些API的介绍放到后面章节再讲,可以先忽略。
public class CameraView extends View {
// 相机
Camera camera = new Camera();
public CameraView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
{
bitmap = Utils.getAvatar(getResources(), IMAGE_WIDTH);
// 沿着x轴旋转45°
camera.rotateX(45);
// 设置相机的z轴位置
camera.setLocation(0, 0, Utils.getZForCamera()); // -8 = -8 * 72
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制下半部分
canvas.save();
// 6. 将图像中心从视图圆点恢复到原来位置
// canvas.translate(OFFSET, OFFSET);
// 5. 图像以视图圆点为中心,旋转-45°
// canvas.rotate(-ROTATE_ANGLE);
// 4. 应用3D变换
camera.applyToCanvas(canvas); // 关键代码
// 3. 裁剪图像,保留x轴下方的灰色区域
canvas.clipRect(-IMAGE_WIDTH, 0, IMAGE_WIDTH, IMAGE_WIDTH);
// 2. 图像以视图圆点为中心,旋转45°
canvas.rotate(ROTATE_ANGLE);
// 1. 将图片的中心移动到视图的圆点位置
canvas.translate(-OFFSET, -OFFSET);
canvas.drawBitmap(bitmap, BITMAP_OFFSET, BITMAP_OFFSET, paint);
canvas.restore();
}
}
4. 应用3D变换(第二张图)
5. 图像以视图圆点为中心,旋转-45°(第三张图)
最后放下合起来的效果(第四张图)
3D变换知识点
这里贴下上面章节缺失的一个方法的定义。
- Android Developer中文网:Camera#setLocation 方法介绍。
public class CameraView extends View {
以上是关于自定义UI 使用Camera做三维变换的主要内容,如果未能解决你的问题,请参考以下文章