当前位置:

首页 > 系统应用 > 8.3.18 Canvas API详解(Part 3)Matrix和drawBitmapMesh

8.3.18 Canvas API详解(Part 3)Matrix和drawBitmapMesh

本节引言: 在Canvas的API文档中,我们看到这样一个方法:drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) 这个 Matrix 里头,其实大有门道。前面讲 Paint 的 API 里 ColorFilter 时,已经提到过 ColorM

本文目录

    本节引言:

    在Canvas的API文档中,我们看到这样一个方法:drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)

    这个 Matrix 里头,其实大有门道。前面讲 Paint 的 API 里 ColorFilter 时,已经提到过 ColorMatrix 颜色矩阵——一个 4 * 5 的矩阵,通过调整矩阵中的数值,就能改动色调、饱和度等效果。今天要说的这个 Matrix,则是另一条线:它更多是配合其他 API 一起使用,用来控制图形和组件的各种变换。比如 Canvas 就提供了前面提到的这个 drawBitmap,可以直接做出矩阵变换的效果。接下来,就一点点把这个东西拆开来看。

    官方API文档:Matrix

    1.Matrix中的几个常用的变换方法

    • setTranslate(float dx, float dy):控制Matrix进行平移
    • setRotate(float degrees, float px, float py):旋转,参数依次是:旋转角度,轴心(x,y)
    • setScale(float sx, float sy, float px, float py):缩放, 参数依次是:X,Y轴上的缩放比例;缩放的轴心
    • setSkew(float kx, float ky):倾斜(扭曲),参数依次是:X,Y轴上的缩放比例

    其实和Canvas变换的方法基本一致,在为Matrix设置了上面的变换后,调用Canvas的 drawBitmap()方法调用矩阵就好~

    2.Matrix使用示例:

    运行效果图:

    代码实现:

    MyView.ja va:

    /**
     * Created by Jay on 2015/11/11 0011.
     */
    public class MyView extends View {
    private Bitmap mBitmap;
        private Matrix matrix = new Matrix();
        private float sx = 0.0f;          //设置倾斜度
        private int width,height;         //位图宽高
        private float scale = 1.0f;       //缩放比例
        private int method = 0;
    public MyView(Context context) {
            this(context, null);
        }
    public MyView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    private void init() {
            mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_meizi);
            width = mBitmap.getWidth();
            height = mBitmap.getHeight();
        }
    @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            switch (method){
                case 0:
                    matrix.reset();
                    break;
                case 1:
                    sx += 0.1;
                    matrix.setSkew(sx,0);
                    break;
                case 2:
                    sx -= 0.1;
                    matrix.setSkew(sx,0);
                    break;
                case 3:
                    if(scale < 2.0){
                        scale += 0.1;
                    }
                    matrix.setScale(scale,scale);
                    break;
                case 4:
                    if(scale > 0.5){
                        scale -= 0.1;
                    }
                    matrix.setScale(scale,scale);
                    break;
            }
            //根据原始位图与Matrix创建新图片
            Bitmap bitmap = Bitmap.createBitmap(mBitmap,0,0,width,height,matrix,true);
            canvas.drawBitmap(bitmap,matrix,null);    //绘制新位图
        }
    public void setMethod(int i){
            method = i;
            postInvalidate();
        }
    }

    布局代码:activity_main.xml:

    
    
    

    MainActivity.ja va:

    public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private Button btn_reset;
        private Button btn_left;
        private Button btn_right;
        private Button btn_zoomin;
        private Button btn_zoomout;
        private MyView myView;
    @Override
        protected void onCreate(Bundle sa vedInstanceState) {
            super.onCreate(sa vedInstanceState);
            setContentView(R.layout.activity_main);
            bindViews();
        }
    private void bindViews() {
            btn_reset = (Button) findViewById(R.id.btn_reset);
            btn_left = (Button) findViewById(R.id.btn_left);
            btn_right = (Button) findViewById(R.id.btn_right);
            btn_zoomin = (Button) findViewById(R.id.btn_zoomin);
            btn_zoomout = (Button) findViewById(R.id.btn_zoomout);
            myView = (MyView) findViewById(R.id.myView);
    btn_reset.setOnClickListener(this);
            btn_left.setOnClickListener(this);
            btn_right.setOnClickListener(this);
            btn_zoomin.setOnClickListener(this);
            btn_zoomout.setOnClickListener(this);
    }
    @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_reset:
                    myView.setMethod(0);
                    break;
                case R.id.btn_left:
                    myView.setMethod(1);
                    break;
                case R.id.btn_right:
                    myView.setMethod(2);
                    break;
                case R.id.btn_zoomin:
                    myView.setMethod(3);
                    break;
                case R.id.btn_zoomout:
                    myView.setMethod(4);
                    break;
            }
        }
    }

    用法非常简单,就不解释了~

    3.drawBitmapMesh扭曲图像

    在API文档中还有这样一个方法: drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)

    参数依次是:

    bitmap:需要扭曲的原位图

    meshWidth/meshHeight:在横/纵向上把原位图划分为多少格

    verts:长度为(meshWidth+1)*(meshHeight+2)的数组,他记录了扭曲后的位图各顶点(网格线交点) 位置,虽然他是一个一维数组,但是实际上它记录的数据是形如(x0,y0),(x1,y1)..(xN,Yn)格式的数据, 这些数组元素控制对bitmap位图的扭曲效果

    vertOffset:控制verts数组从第几个数组元素开始对bitmap进行扭曲(忽略verOffset之前数据 的扭曲效果)

    代码示例:

    运行效果图:

    代码实现:

    /**
     * Created by Jay on 2015/11/11 0011.
     */
    public class MyView extends View {
    //将水平和竖直方向上都划分为20格
        private final int WIDTH = 20;
        private final int HEIGHT = 20;
        private final int COUNT = (WIDTH + 1) * (HEIGHT + 1);  //记录该图片包含21*21个点
        private final float[] verts = new float[COUNT * 2];    //扭曲前21*21个点的坐标
        private final float[] orig = new float[COUNT * 2];    //扭曲后21*21个点的坐标
        private Bitmap mBitmap;
        private float bH,bW;
    public MyView(Context context) {
            this(context, null);
        }
    public MyView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    private void init() {
            mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_wuliao);
            bH = mBitmap.getWidth();
            bW = mBitmap.getHeight();
            int index = 0;
            //初始化orig和verts数组。
            for (int y = 0; y <= HEIGHT; y++)
            {
                float fy = bH * y / HEIGHT;
                for (int x = 0; x <= WIDTH; x++)
                {
                    float fx = bW * x / WIDTH;
                    orig[index * 2 + 0] = verts[index * 2 + 0] = fx;
                    orig[index * 2 + 1] = verts[index * 2 + 1] = fy;
                    index += 1;
                }
            }
            //设置背景色
            setBackgroundColor(Color.WHITE);
        }
    @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, verts
                    , 0, null, 0, null);
        }
    //工具方法,用于根据触摸事件的位置计算verts数组里各元素的值
        private void warp(float cx, float cy)
        {
            for (int i = 0; i < COUNT * 2; i += 2)
            {
                float dx = cx - orig[i + 0];
                float dy = cy - orig[i + 1];
                float dd = dx * dx + dy * dy;
                //计算每个座标点与当前点(cx、cy)之间的距离
                float d = (float)Math.sqrt(dd);
                //计算扭曲度,距离当前点(cx、cy)越远,扭曲度越小
                float pull = 80000 / ((float) (dd * d));
                //对verts数组(保存bitmap上21 * 21个点经过扭曲后的座标)重新赋值
                if (pull >= 1)
                {
                    verts[i + 0] = cx;
                    verts[i + 1] = cy;
                }
                else
                {
                    //控制各顶点向触摸事件发生点偏移
                    verts[i + 0] = orig[i + 0] + dx * pull;
                    verts[i + 1] = orig[i + 1] + dy * pull;
                }
            }
            //通知View组件重绘
            invalidate();
        }
    @Override
        public boolean onTouchEvent(MotionEvent event)
        {
            //调用warp方法根据触摸屏事件的座标点来扭曲verts数组
            warp(event.getX(), event.getY());
            return true;
        }
    }

    实现流程分析:

    先把一个关键点捋顺:这个verts数组里到底装的是什么?拿最前面的两个相邻元素来说,比如verts[0]和verts[1],它们其实对应的正是第一个点的x坐标和y坐标。把这一层关系看明白了,为什么会有21 * 21个点、为什么数组长度刚好等于这个数量再乘以2,答案也就顺理成章了。这样一来,初始化这部分自然就不难理解了。

    接着我们再来看看根据触摸事件计算verts数组元素的值的实现: 获得触摸点的x,y坐标,拿这个值去减对应点的x,y只,计算出触摸点和每个坐标点的距离 然后计算所谓的扭曲度:80000 / ((float) (dd * d));扭曲度 >= 1的,直接让该坐标 点指向这个触摸点,< 1的,则让各个顶点向触摸点发生偏移,然后再调用invalidate()重绘~ 大概就这样~多思考思考,如果还是不理解就算了~知道有这东西就好!

    4.本节示例下载:

    CanvasDemo3.zip

    CanvasDemo4.zip

    本节小结:

    本节内容大部分摘自——李刚《Android》疯狂讲义,可能稍微容易理解一点吧~ Matrix应该大部分的童鞋都能看懂,而drawBitmapMesh扭曲图像则可能需要一点 时间消化消化,看不懂也没什么哈~嗯,本节就到这里,谢谢

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    系统应用
    相关文章 更多
    Yandex官网入口及国际版登录指南
    Yandex官网入口及国际版登录指南

    Yandex官方网站快速入口是https://www.yondex.net/,该平台提供多语言搜索引擎、新闻聚合、翻译工具、图片视频检索及个性化推荐等核心服务,并集成地图、云存储、邮箱、日历等实用功能,支持移动端适配与夜间模式,界面简洁且加载迅速。

    Yandex官网入口及最新访问地址
    Yandex官网入口及最新访问地址

    Yandex搜索引擎官方入口是https://www.yondex.net/,该平台具备强大的多语言搜索能力、集成化的服务生态体系以及智能化的搜索体验优化功能。

    Yandex国际版入口及官网链接
    Yandex国际版入口及官网链接

    Yandex国际版官网为https://www.yondex.net/,提供英文界面,适合非俄语用户;2.https://www.yondex.net/为全球短域名,可自动跳转至对应地区版本;3.俄罗斯本地用户使用https://www.yondex.net/,内容以俄语为主,本地化程度高;4.普通用户可根据语言和需求选择相应入口。

    Yandex官网入口及网页版访问教程
    Yandex官网入口及网页版访问教程

    Yandex官方在线入口是https://www.yondex.net/,提供网页搜索、新闻、图片视频检索、地图导航等服务,并集成翻译、邮箱、云存储、日历等工具,支持个性化首页定制与电商比价功能。

    妖精漫画官网登录入口及首页指南
    妖精漫画官网登录入口及首页指南

    妖精漫画官网登录入口为https://www.yaojingmh.com。该平台资源丰富,涵盖多种题材,更新及时,分类清晰,支持多模式阅读与个性化收藏,具备评论、书架、弹幕等互动功能,界面简洁流畅,适配移动端,提供良好阅读体验。

    Yandex官网入口及最新搜索平台访问方式
    Yandex官网入口及最新搜索平台访问方式

    Yandex搜索平台最新访问入口是https://www.yondex.net/,该网站界面简洁,支持多语言切换和智能提示,具备强大的自然语言处理与多样化内容检索功能,并集成翻译、云存储、邮件等附加服务。

    Yandex搜索引擎官网入口及登录教程
    Yandex搜索引擎官网入口及登录教程

    俄罗斯Yandex搜索引擎创建于1993年,是俄语区主导的搜索服务,官网为https://www.yondex.net/,用户可直接访问并切换语言,无需注册即可使用其搜索、地图、邮箱等集成服务。

    Yandex浏览器官网登录与在线体验入口
    Yandex浏览器官网登录与在线体验入口

    Yandex浏览器官方网站登录入口是https://www.yondex.net/,提供多语言下载、账户同步、广告拦截、Turbo模式及隐私保护等功能,支持跨设备数据同步与安全浏览。

    2026年AO3镜像最新地址及稳定入口
    2026年AO3镜像最新地址及稳定入口

    ao3镜像2026最新入口包括主站archiveofourown.org、镜像站ao3mirror.com和xiaozhan.icu,建议收藏备用并根据网络情况切换使用。

    Win10搜索不到蓝牙耳机的排查与连接方法
    Win10搜索不到蓝牙耳机的排查与连接方法

    面向在Windows 10系统中无法发现蓝牙耳机的用户,本文提供从耳机配对模式到系统设置的完整排查路径。首先确保耳机已充电并进入可被搜索的配对状态,断开与其他设备的连接;随后在Win10设置中开启蓝牙并添加设备。若蓝牙开关缺失或适配器异常,需通过设备管理器检查驱动状态并更新,以恢复正常的蓝牙音频设备识别功能。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。