Android 自定义View(二):画布、画笔、路径(遮罩)以及Sufaceview

news2024/11/25 12:42:56

目录

1)画布是什么?画布如何使用?
2)画笔是什么,画笔如何生成呢?
3)如何画圆、画文字、画矩形
4)路径(Path)遮罩
5)Sufaceview(使用子线程绘画)

一、画布是什么?画布如何使用?

为什么需要画布?前面我们使用MyEditText这些也不需要呀。因为EditText是已经进行了绘制,我们是继承过来进行二次改造开发。

这次学习画布的知识点,我们继承一个View,一个空白内容的控件去开发。

Canvas类在Android中扮演着画布的角色,它提供了各种绘制方法,如绘制线条、矩形、圆形、文本等。通过Canvas,开发者可以在屏幕上绘制出各种图形界面元素。

1.1、在使用之前,我们先了解一下view的方法回调,方便我们知道在绘制期间,应该把逻辑写在哪个方法里面。

在这里插入图片描述

//这些方法的作用:总结起来,这些方法分别用于处理视图的初始化、布局、测量、绘制以及生命周期的管理。通过重写这些方法,可以实现对自定义视图的完全控制。

class MyView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private var mWidth = 0f
    private var mHeight = 0f

    //当view的最终尺寸确定之后进行调用。一般用于记录实际的宽高
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mWidth = w.toFloat()
        mHeight = h.toFloat()
    }


    //当从xml创建view的时候,创建完毕后调用
    override fun onFinishInflate() {
        super.onFinishInflate()
    }

    //当视图被附加到窗口时调用。在该方法中可以执行视图生命周期相关的操作,例如注册监听器或启动动画。
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
    }

    //测量视图的大小。在该方法中需要根据传入的 widthMeasureSpec 和 heightMeasureSpec 参数来计算并设置视图的宽度和高度。
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
    //布局视图的位置。在该方法中需要根据传入的参数来确定视图的左上角和右下角坐标,并将子视图放置在正确的位置。
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
    }

    //【重点关注】绘制视图的内容。在该方法中可以使用传入的 canvas 对象进行绘制操作,例如绘制文本、图形或图片等。
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //在这里画图。注意不要在这个方法里面创建对象,因为绘制里面如果存在耗时操作会导致掉帧并创建大量对象的情况出现
        canvas.apply {
            drawAxises(this)
        }

    }

    //当视图从窗口中分离时调用。在该方法中可以执行与视图生命周期相关的清理操作,例如取消监听器或停止动画。
    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
    }

}

绘制的时候,也要注意层次,自定义view控制不好,层次太多,就会导致绘制时间长,也就是会掉帧。
在这里插入图片描述
当我们继承了View以后,画布也就自然有了,我们可以看到override fun onDraw(canvas: Canvas)方法里面提供了canvas,接下来我们就可以画图了,可以花圆,画文字等等

但现在只有画布,我们需要一个画笔,才能进行绘制。接下来我们了解一下画笔。

二、画笔是什么,画笔如何生成呢?

通过画笔,我们可以在画布(Canvas)上绘制出各种图形,如线条、圆形、矩形、文本等,并可以设置这些图形的颜色、粗细、样式等属性。

(1)创建画笔

//实线线条的画笔 
private val solidLinePaint = Paint().apply {  
    style = Paint.Style.STROKE // 设置画笔的绘制样式为描边(不填充)  
    strokeWidth = 5f // 设置线条的宽度为5个浮点单位  
    color = Color.WHITE // 设置线条的颜色为白色  
}


//文本的画笔 (textPaint)
private val textPaint = Paint().apply {  
    textSize = 50f // 设置文本的字体大小为50个浮点单位  
    typeface = Typeface.DEFAULT_BOLD // 设置文本的字体为默认加粗字体  
    color = Color.WHITE // 设置文本的颜色为白色  
}

//虚线线条的画笔 (dashedLinePaint)
private val dashedLinePaint = Paint().apply {  
    style = Paint.Style.STROKE // 设置画笔的绘制样式为描边(不填充)  
    pathEffect = DashPathEffect(floatArrayOf(10f, 10f), 0f) // 设置路径效果为虚线  
    strokeWidth = 5f // 设置线条的宽度为5个浮点单位  
    color = Color.YELLOW // 设置线条的颜色为黄色  
}

(2)使用画笔

  //绘制视图的内容。在该方法中可以使用传入的 canvas 对象进行绘制操作,例如绘制文本、图形或图片等。
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //在这里画图。
        canvas.apply {
            drawAxises(this)
        }

    }
    private fun drawAxises(canvas: Canvas){
        //绘图的方式:在绝对坐标系,也就是从0,0开始
        //画线
//        canvas.drawLine(100f,100f,100f,400f,solidLinePaint)
        //移动画布到居中位置
        canvas.withTranslation(mWidth/2,mHeight/2) {
            //如果我们要绘制一条直线,那么就是从-mWidth/2,到mWidth/2
            drawLine(-mWidth/2,0f,mWidth/2,0f,solidLinePaint)
        }
    }

三、如何画圆、画文字、画矩形

private fun drawLabel(canvas: Canvas){
        canvas.apply {
            drawRect(100f,100f,600f,250f,solidLinePaint)//画矩形
            drawText("大家好",120f,195f,textPaint)//画文字
        }
    }

    private fun drawDashedCircle(canvas: Canvas){
        //画圆,需要有半径,可以自己填写。
        canvas.withTranslation(mWidth/2,mHeight/2) {
            drawCircle(0f,0f,0f,dashedLinePaint)
        }
    }

四、如何使用路径实现遮罩

Path类是Android图形编程中非常核心的一个类,它用于表示一系列的图形路径,这些路径可以包含直线、曲线、圆形等多种形状,并可以用于绘制、剪裁或者定义复杂的图形轮廓。

使用Path,你可以构建出复杂的图形轮廓,并通过Canvas的drawPath(Path path, Paint paint)方法将这些图形绘制到屏幕上。比如,你可以绘制出平滑的曲线、不规则的多边形,甚至是基于数学函数的图形等。

Path提供了多种路径变换的方法,如平移(translate)、缩放(scale)、旋转(rotate)等,使得在绘制复杂图形时,可以对图形的各个部分进行精细的控制和调整。

除了绘制图形,Path还可以用于定义剪裁区域。通过Canvas的clipPath(Path path)方法,可以将绘制的区域限制在Path定义的路径内部。这对于创建特定形状的窗口、或者在复杂背景下只显示特定区域的内容非常有用。

下面我们做一个案例:通过路径绘制一个矩形以及圆形,通过裁剪路径达到一个遮罩的效果
在这里插入图片描述在这里插入图片描述

package com.example.mymediaplayer.myview

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import com.example.mymediaplayer.R
import kotlin.random.Random

class MyView2 @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    private val faceBitmap =
        ContextCompat.getDrawable(context, R.drawable.baseline_clear_24)?.toBitmap(300, 300)
    private var faceX = 0f
    private var faceY = 0f

    private val path = Path()
    private val paint = Paint()

    private fun randomPosition() {
        faceX = Random.nextInt(width - 300).toFloat()
        faceY = Random.nextInt(height - 300).toFloat()

    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.apply {
            faceBitmap.let { drawBitmap(it!!, faceX, faceY, null) }
            drawPath(path, paint)

        }
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        event?.apply {
            Log.d("action", "onTouchEvent: " + action)
            when (action) {

                MotionEvent.ACTION_DOWN -> {
                    randomPosition()
                    //重置路径对象,清除之前的路径。
                    path.reset()
                    //添加一个矩形路径,表示整个视图的范围。
                    path.addRect(0f, 0f, width.toFloat(), height.toFloat(), Path.Direction.CW)
                    //添加一个圆形路径,以当前触摸点为中心,半径为 400 像素。
                    path.addCircle(x, y, 400f, Path.Direction.CCW)
                }

                MotionEvent.ACTION_MOVE -> {
                    path.reset()
                    path.addRect(0f, 0f, width.toFloat(), height.toFloat(), Path.Direction.CW)
                    path.addCircle(x, y, 400f, Path.Direction.CCW)
                }

                MotionEvent.ACTION_UP -> {
                    path.reset()
                }

                else -> {}
            }
            invalidate()//通知系统重新绘制视图,即调用 onDraw() 方法。
        }
        return true//返回 true 表示已经处理了触摸事件,不再传递给其他监听器或父视图。
    }


}

当手指按下屏幕时,在视图上随机生成一个位置,并绘制一个以该位置为中心、半径为 400 像素的圆形路径。
当手指在屏幕上移动时,更新圆形路径的位置,使其跟随手指移动。
当手指抬起时,清除路径,不再显示圆形。
Path.Direction.CW和Path.Direction.CCW的相互使用,就达到了裁剪的效果。

五、Sufaceview

为什么这里要讲Sufaceview,因为Sufaceview可以在子线程上绘制,View只能在主线程。

1)View:View的绘制通常是在UI线程(主线程)上进行的。当需要更新视图内容时,UI线程会负责执行绘图操作,这可能会导致在复杂或频繁的绘图操作中UI线程被阻塞,进而影响应用的响应性和流畅性。
3)SurfaceView:SurfaceView则不同,它拥有自己独立的绘制表面(Surface),可以在一个子线程中进行绘制操作。这种机制使得SurfaceView在需要频繁更新画面或进行复杂计算时,能够避免阻塞UI主线程,从而提高应用的性能和响应性。

7.1 使用View模拟卡顿的情况

比如我们在界面上放置一个动态加载圈,会不停的转圈圈加载,同时每次点击屏幕绘制3000个圆,我们就会注意到,加载圈会在创建圆的时候,卡顿一下。
在这里插入图片描述在这里插入图片描述


package com.example.mymediaplayer.myview

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View

class MyView3 @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    private var centerX = 0f
    private var centerY = 0f

    private val colors = arrayOf(Color.RED,Color.GREEN,Color.YELLOW,Color.MAGENTA,Color.BLUE,Color.GRAY)

    private val paint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 5f
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        repeat(2000){
            paint.color = colors.random()
            canvas?.drawCircle(centerX,centerY,it.toFloat()/5,paint)
        }
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        centerX = event?.x?:0f
        centerY = event?.y?:0f
        invalidate()
        return super.onTouchEvent(event)
    }
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".hilt.MainActivity">

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="220dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.559"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.example.mymediaplayer.myview.MyView3
        android:id="@+id/myView3"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

改用SufaceView

package com.example.mymediaplayer.myview

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.SurfaceView

class MySufaceView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : SurfaceView(context, attrs) {

    private var centerX = 0f
    private var centerY = 0f

    private val colors = arrayOf(
        Color.RED,
        Color.GREEN,
        Color.YELLOW,
        Color.MAGENTA,
        Color.BLUE,
        Color.GRAY)

    private val paint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 5f
    }


    override fun onTouchEvent(event: MotionEvent?): Boolean {
        centerX = event?.x?:0f
        centerY = event?.y?:0f
        val canvas = holder.lockCanvas()//获取一个画布对象 canvas,并设置画布背景颜色为白色(canvas.drawColor(Color.WHITE))。
        canvas.drawColor(Color.WHITE)
        repeat(2000){//循环2000次
            paint.color = colors.random()
            canvas?.drawCircle(centerX,centerY,it.toFloat()/5,paint)
        }
        holder.unlockCanvasAndPost(canvas)//将画布内容显示到屏幕上。
        return super.onTouchEvent(event)
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1986088.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UWB实操:使用 litepoint 定制UWB信号,BPRF,HPRF,mean PRF,SFD,gap,PSDU,STS

使用 litepoint 定制UWB信号 预备知识: Technology选择UWBP VSG -> WaveGen ->UWBP Wave settings G

2024.8.6 作业

1> 使用消息队列完成两个进程之间相互通信 snd.c #include <myhead.h>struct msgbuf {long mtype;char mtext[1024]; };#define SIZE sizeof(struct msgbuf)-sizeof(long)int main(int argc,const char *argv[]) {pid_t pid fork();if(pid-1){perror("fork er…

【C++入门(下)】—— 我与C++的不解之缘(二)

前言 接上篇&#xff0c;继续来学习C&#xff0c;本篇内容大概有 引用&#xff0c;inline 和 nullptr。 六、引用&#xff1a; 6.1、引用的定义 引用不是新定义一个变量&#xff0c;而是给已存在的变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它…

SQL基础命令

目录 查看版本 root登录 查看用户 数据库清单 创建数据库 选择数据库 删除数据库 退出 MySQL 查看版本 mysql --version root登录 mysql -uroot -p 查看用户 select user()&#xff1b; 数据库清单 show databases; 创建数据库 # create database xxx; create dat…

heic格式批量转化jpg,这几个方法简单好上手!

在这个数字化时代&#xff0c;手机摄影已成为我们记录生活、分享美好的重要方式。然而&#xff0c;苹果用户可能会遇到一个头疼的问题——拍摄的照片默认保存为HEIC格式&#xff0c;这种格式虽然能大幅节省存储空间&#xff0c;但在非苹果设备上查看或编辑时却不太方便。别担心…

8月开始|660/880/严选题45天强化带刷计划

45天刷完《严选题》《660》《880》&#xff1f; 可能吗&#xff1f; 是不是又在制造焦虑&#xff1f; 别急&#xff0c;其实严选题《660》《880》的核心知识点并不多&#xff0c;45天完全能够刷完&#xff0c;下面就是帮大家整理总结的这些习题册的核心知识点和重点题&#…

【多线程-从零开始-肆】线程安全、加锁和死锁

进程状态 进程状态&#xff1a; 就绪&#xff1a;正在 CPU 上执行&#xff0c;或者随时可以去 CPU 上执行阻塞&#xff1a;暂时不能参与 CPU 的执行 Java 的线程&#xff0c;对应状态做了更详细的区分&#xff0c;不仅仅是就绪和阻塞了 六种状态&#xff1a; NEW 当前 Thread…

vulnhub靶机实战_DC-8

一、靶机下载 靶机下载链接汇总&#xff1a;https://download.vulnhub.com/使用搜索功能&#xff0c;搜索dc类型的靶机即可。本次实战使用的靶机是&#xff1a;DC-8系统&#xff1a;Debian下载链接&#xff1a;https://download.vulnhub.com/dc/DC-8.zip 二、靶机启动 下载完…

C++编程基础的学习

Qt跨平台特性 在深入探讨C编程的基础知识之前&#xff0c;我们首先需要了解Qt框架的跨平台特性。Qt是一个功能强大的跨平台应用程序框架&#xff0c;它允许开发者编写一次代码&#xff0c;然后在多个平台上运行&#xff0c;包括Windows、macOS、Linux、iOS、Android等。这种跨…

Linux网络编程3

并发服务器 1.TCP多进程并发服务器 服务器端&#xff1a; 客户端&#xff1a; 2.TCP多线程服务器 服务器端&#xff1a; 客户机端&#xff1a; 需要学习的函数还有 1. send() 函数 send() 函数用于在套接字上发送数据。它是网络编程中发送数据到对端的主要函数之一。 函数…

人像修图-高低频磨皮

原理 将图像分成高频图层&#xff08;处理纹理细节&#xff09;和低频图层&#xff08;处理光影和光影&#xff09;&#xff0c;以达到修饰时互不干扰 步骤 复制两个图层 在低频图层建立高斯模糊&#xff1a;滤镜->模糊->高斯模糊。注意半径一般根据皮肤占比&#xf…

day 20进程

一、程序和进程的区别 程序&#xff1a;保存在磁盘空间中的的一段代码的集合&#xff0c;死的 进程&#xff1a;是一个程序动态执行的过程&#xff0c;包括进程的创建、调度和消亡的过程 二、进程相关的命令 PID:进程的标识符(进程的ID) PPID:父进程的ID号 三、进程的创建…

redis的数据结构与对象

简单动态字符串 文章目录 简单动态字符串SDS的定义SDS的结构图示结构SDS字段解析SDS的特点 SDS和字符串的区别常数复杂度获取字符串的长度杜绝缓冲区的溢出减少修改字符串时的内存分配次数二进制安全兼容部分c字符串函数总结 链表链表和链表节点的实现链表节点&#xff08;list…

全球手机基站位置数据,包含(2G-5G)基站

OpenCellID 是一个由社区维护的项目&#xff0c;它提供了一个开放的数据集&#xff0c;包含全球各地的移动通信基站信息。这个项目对于需要获取蜂窝网络基础设施详细信息的研究人员、开发者以及组织来说非常有用。这些信息可以被用来进行各种分析和应用开发&#xff0c;例如改进…

Python 在开发中的设计模式有哪些?怎样使用?

大家好&#xff01;我是爱摸鱼的小鸿&#xff0c;关注我&#xff0c;收看每期的编程干货。 今天我们要聊点硬核的——设计模式。不过&#xff0c;不用担心&#xff0c;我会带着热情来跟你分享这些看似枯燥的知识点。让我们一起从“代码搬砖工”蜕变成“代码艺术家”吧&#xff…

Redis面试题大全

文章目录 Redis有哪几种基本类型Redis为什么快&#xff1f;为什么Redis6.0后改用多线程?什么是热key吗&#xff1f;热key问题怎么解决&#xff1f;什么是热Key&#xff1f;解决热Key问题的方法 什么是缓存击穿、缓存穿透、缓存雪崩&#xff1f;缓存击穿缓存穿透缓存雪崩 Redis…

python爬虫预备知识三-多进程

python实现多进程的方法&#xff1a;fork、multiprocessing模块创建多进程。 os.fork方法 os.fork方法只适合于unix/linux系统&#xff0c;不支持windows系统。 fork方法调用一次会返回两次&#xff0c;原因在于操作系统将当前进程&#xff08;父进程&#xff09;复制出一份…

ESP8266使用舵机以及16路PWM舵机PCA 9685的使用方式

PWM全称 50Hz也就是一秒内变换50次 根据上面的公式 一个高电平一个低电平叫一个脉冲。 例如每个脉冲占20毫秒&#xff0c;那么他的频率是多少&#xff1f; 就是用1去除以他的周期&#xff0c;也就是我们上面说的20&#xff0c;那么就是除0.02,1秒等于1000毫秒&#xff0c;20…

网络安全 - 应急响应检查表

前言 本项目旨在为应急响应提供全方位辅助&#xff0c;以便快速解决问题。结合自身经验和网络资料&#xff0c;形成检查清单&#xff0c;期待大家提供更多技巧&#xff0c;共同完善本项目。愿大家在应急之路一帆风顺。 图片皆来源于网络&#xff0c;如有侵权请联系删除。 一…

南山智尚10亿元定增质疑声连连,与控股股东超70亿资金往来引瞩目

《港湾商业观察》施子夫 王璐 近期&#xff0c;南山智尚&#xff08;300918.SZ&#xff09;发布了《向特定对象发行A股股票募集说明书(修订稿)》。 据了解&#xff0c;公司此次拟募集资金总额不超过10亿元&#xff0c;扣除发行费用后的募集资金净额将全部用于年产8万吨高性能…