自定义UI 使用Camera做三维变换

Posted Notzuonotdied

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义UI 使用Camera做三维变换相关的知识,希望对你有一定的参考价值。

系列文章目录

  1. 自定义UI 基础知识
  2. 自定义UI 绘制饼图
  3. 自定义UI 圆形头像
  4. 自定义UI 自制表盘
  5. 自定义UI 简易图文混排
  6. 自定义UI 使用Camera做三维变换


前言

这系列的文章主要是基于扔物线的HenCoderPlus课程的源码来分析学习。



这一篇文章主要介绍的是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做三维变换的效果图。

左边是原图,右边是3D变换效果图

定义绘制的位置

坐标轴的单位都是px
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);
    }
}

变换流程解析

摘录自:HenCoder Android 开发进阶:自定义 View 1-4 Canvas 对绘制的辅助

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;
}

先放上两部分单独的绘制效果:

左边是正常的图片,右边是经过Camera变换后的图形

左上部分变换

1. 将图片的中心移动到视图的圆点位置
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();
    }
}
2. 图像以视图圆点为中心,旋转45°
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


4. 图像以视图圆点为中心,旋转-45°
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();
    }
}

右下部分变换

右下部分基本和上一章节一致,这里仅仅介绍不一样的地方。

2. 图像以视图圆点为中心,旋转45°
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();
    }
}
3. 裁剪图像,保留x轴下方的灰色区域(第一张图)
4. 应用3D变换(第二张图)
5. 图像以视图圆点为中心,旋转-45°(第三张图)
最后放下合起来的效果(第四张图)

3D变换知识点

这里贴下上面章节缺失的一个方法的定义。

public class CameraView extends View {
  

以上是关于自定义UI 使用Camera做三维变换的主要内容,如果未能解决你的问题,请参考以下文章

自定义UI 使用Camera做三维变换

自定义UI 简易图文混排

自定义UI 简易图文混排

自定义UI 属性动画

自定义UI 属性动画

自定义UI 属性动画