IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android APP完整基础教程(15)图形系统-Canvas绘图 -> 正文阅读

[移动开发]Android APP完整基础教程(15)图形系统-Canvas绘图

1 绘图基础

1.1 绘图基础类解读与实战

绘图基础类涉及 Canvas(画布)、Paint(画笔)、Path(多条直线任意图形)。

@1 Canvas类解读

Android绘图方式是继承View组件,并重写它的onDraw()方法来实现绘制。Canvas的绘制方法有:

API详细内容可以参照文档:Android Canvas 各种drawXXX绘制方法

除了绘制drawXXX方法,还有rotate(旋转)、scale(缩放)、skew(倾斜)、translate(平移)来对其进行坐标变换。

关于Canvas画布类,更多解读可查看官方文档:Android Canvas类详细解读

@2 Paint类解读

Paint表示Canvas画布上的画笔,主要用于设置颜色、粗细、绘制风格和填充风格等。Paint的设置方式有:

?API详细内容可以参照文档:Android Paint 各种setXXX方法解读

关于Paint画笔类,更多解读可查看官方文档:Android Paint类详细解读

@3 Path类解读

Path可以预先在View上将多个点连接成一条“路径”,然后调用Canvas的drawPath方法沿着路径绘制任意图形,灵活度很高。Path中的方法有:

?API详细内容可以参照官方文档:Android Path类详细解读

@4 基础类实战(基于Canvas、Paint、Path绘制图形训练)

绘制内容及程序最终运行结果如下所示:

关于该程序,自定义View的关键代码如下所示:

public class MyView extends View {
    private static final String TAG = "MyView";
    private Paint paint =new Paint();
    private Path path1 = new Path();
    private Path path2 = new Path();
    private Path path3 = new Path();

    public MyView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);
        paint.setAntiAlias(true);//抗锯齿
        paint.setColor(Color.RED);//画笔颜色
        paint.setStyle(Paint.Style.STROKE);//描边模式
        paint.setStrokeWidth(4f);//设置画笔粗细度
        paint.setTextSize(52f);
        drawsth(canvas,paint,path1,0f);
        paint.setStyle(Paint.Style.FILL);//填充模式
        paint.setColor(Color.BLUE);//画笔颜色
        drawsth(canvas,paint,path2,220f);
        paint.setColor(Color.MAGENTA);
        paint.setStyle(Paint.Style.STROKE);//描边模式
        drawTextOnCurve(canvas,paint,path3);
    }
    //绘制曲线和曲线上的文字
    private void drawTextOnCurve(Canvas canvas, Paint paint,Path path){
        path.moveTo(440f,10f);
        path.lineTo(460f,80f);
        path.lineTo(490f,150f);
        path.lineTo(550f,250f);
        path.lineTo(700f,400f);
        canvas.drawPath(path,paint);
        canvas.drawTextOnPath("曲线上的文字",path,10f,10f,paint);
    }

    //绘制基本图形、Path不规则图形和文字
    private void drawsth(Canvas canvas, Paint paint,Path path,float offset){

        //paint绘制圆形,参数:圆心坐标(x,y)半径r,画笔paint
        canvas.drawCircle(100f+offset,100f,90f,paint);
        //paint绘制矩形,参数:左上角坐标(x,y),右下角坐标(x,y),画笔paint
        canvas.drawRect(10f+offset,200f,210f+offset,400f,paint);
        //paint绘制圆角矩形,参数:左上角坐标(x,y),右下角坐标(x,y),x方向上的圆角半径,y方向上的圆角半径,画笔paint
        canvas.drawRoundRect(10f+offset,410f,210f+offset,610f,40f,40f,paint);
        //paint绘制椭圆,参数:左上角坐标(x,y),右下角坐标(x,y)[参数表示矩形内切的椭圆],画笔paint
        canvas.drawOval(10f+offset,620f,210f+offset,720f,paint);

        //Path绘制三角形,moveto表示第1个坐标,lineto分别表示第2、3个坐标。以此类推。
        path.moveTo(110f+offset,730f);
        path.lineTo(10f+offset,930f);
        path.lineTo(210f+offset,930f);
        path.close();
        canvas.drawPath(path,paint);
        //Path绘制不规则多点多边形
        path.moveTo(10f+offset,950f);
        path.lineTo(50f+offset,1000f);
        path.lineTo(200f+offset,970f);
        path.lineTo(150f+offset,1070f);
        path.lineTo(10f+offset,1110f);
        path.lineTo(210f+offset,1130f);
        path.close();
        canvas.drawPath(path,paint);

        //注意:这个坐标(x,y)并不是文字的左上角,而是一个与左下角比较接近的位置。
        canvas.drawText("测试文字",10f+offset,1190f,paint);
    }
}

在MainActivity中实现代码为:

public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
    }
}

说明:该程序利用关键类Canvas、Paint、Path类来绘制基本图形、文字和沿曲线绘制文字。

1.2?PathEffect解读

@1?PathEffect类解读

PathEffect派生类主要有:

ComposePathEffect:Android ComposePathEffect类详细解读,组合2种效果。
CornerPathEffect:Android CornerPathEffect类详细解读,平滑过渡。
DashPathEffect:Android DashPathEffect类详细解读,线段虚线化。
DiscretePathEffect:Android DiscretePathEffect类详细解读,打散线段。
PathDashPathEffect:Android PathDashPathEffect类详细解读,使用Path图形来填充当前的路径。
SumPathEffect:Android SumPathEffect类详细解读,叠加2种效果。

@2?PathEffect类实战

分别展示以上几种PathEffect派生类的效果,最终呈现 如下所示:

关于该程序,自定义View的关键代码如下所示:

public class MyView extends View {
    private static final String TAG = "MyView";
    private Paint paint =new Paint();
    private PathEffect[] effects = new PathEffect[7];

    private int[] colors = new int[]{Color.BLACK,Color.BLUE,
    Color.RED,Color.GREEN,Color.CYAN,Color.MAGENTA,Color.GRAY};

    private String[] effect = new String[]
            {"null","CornerPathEffect","DiscretePathEffect","DashPathEffect",
                    "PathDashPathEffect","ComposePathEffect","SumPathEffect"};
    private Path path = new Path();

    public MyView(Context context) {
        super(context);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4f);
        paint.setTextSize(38f);
        path.moveTo(0f,0f);
        for(int i=0;i<=25;i++){
            path.lineTo(i*25f,(float)(Math.random()*90));
        }
        effects[0] = null;
        effects[1] = new CornerPathEffect(10f);
        effects[2] = new DiscretePathEffect(3.0f,5.0f);
        effects[3] = new DashPathEffect(new float[]{20f,10f,5f,10f},0f);
        Path p = new Path();
        p.addRect(0f,0f,8f,8f,Path.Direction.CCW);
        effects[4] = new PathDashPathEffect(p,12f,3f,PathDashPathEffect.Style.ROTATE);
        effects[5] = new ComposePathEffect(effects[3],effects[4]);
        effects[6] = new SumPathEffect(effects[2],effects[4]);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        canvas.translate(10f,10f);

        for(int k=0;k<effects.length;k++){
            paint.setColor(colors[k]);
            canvas.drawText(k+" "+effect[k],10f,50f+k*90f,paint);
        }
        canvas.translate(400f,0f);
        for(int j=0;j<effects.length;j++){
            paint.setPathEffect(effects[j]);
            paint.setColor(colors[j]);
            canvas.drawPath(path,paint);
            canvas.translate(0f,90f);
        }
        //注意:这里如果不设置为null重绘时paint依然持有effect效果
        paint.setPathEffect(null);
    }
}

在MainActivity中实现代码同上,依然为:

public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
    }
}

该程序主要展现几种PathEffect派生类的实际效果。

1.3?绘制简单的动画内容

@1 动起来的View

以上只是静态图像的显示,如果要动起来,需要让View组件上的绘图不断刷新,通知View重绘的方法是:invalidate(UI线程)和postInvalidate(非UI线程)

为了保留之前的绘制内容,采用双缓冲技术。它的主要原理是:当一些列动画争先显示时,程序在不断改变它,前面的画面还没有显示完,程序又请求重新绘制,这就回导致屏幕不停闪烁。为了避免闪烁,可以使用双缓冲技术,将要处理的图片都在内存中处理好之后,再将其显示到屏幕上。这样显示出来的总是完整的图像,不会出现闪烁现象。而在android系统中可以理解为:当程序需要在指定View上绘制时,不直接绘制,而是先绘制在内存中的Bitmap图片上,等到内存中Bitmap图片绘制好后,一次性将Bitmap图片绘制到View组件上。

@2 实战(使用双缓冲的绘图板)

实现功能为:触摸屏幕时候沿着触摸的痕迹自由绘制内容。效果如下:

关于该程序,自定义View的关键代码如下所示:

public class MyView extends View {
    private static final String TAG = "MyView";
    private Paint paint =new Paint(Paint.DITHER_FLAG);
    private Paint bitmapPaint = new Paint();
    private Path path = new Path();
    private float preX = 0.0f;
    private float preY = 0.0f;
    private Bitmap cacheBitmap;
    private Canvas cacheCanvas = new Canvas();

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context,AttributeSet attrs){
        super(context,attrs);
    }

    public MyView(Context context,AttributeSet attrs,int defStyleAttr){
        super(context,attrs,defStyleAttr);
    }

    public void clear(){
        cacheBitmap.eraseColor(Color.WHITE);
        invalidate();
    }

    public void init(int w,int h){
        //双缓冲绘制设置
        cacheBitmap = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888);
        cacheCanvas.setBitmap(cacheBitmap);
        paint.setColor(Color.RED);//画笔颜色
        paint.setStyle(Paint.Style.STROKE);//描边模式
        paint.setStrokeWidth(1f);//设置画笔粗细度
        paint.setAntiAlias(true);//抗锯齿
        paint.setDither(true);//防抖动
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        //绘制过去内容
        canvas.drawBitmap(cacheBitmap,0f,0f,bitmapPaint);
        //绘制当下内容
        canvas.drawPath(path,paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch(event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                path.moveTo(x,y);
                preX = x;
                preY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                path.lineTo(x,y);
                preX = x;
                preY = y;
                break;
            case MotionEvent.ACTION_UP:
                //将之前的轨迹图像记录在cacheCanvas中的cacheBitmap中
                cacheCanvas.drawPath(path,paint);
                path.reset();
                break;
        }
        invalidate();//刷新,下一帧
        return true;
    }
}

在MainActivity中实现代码如下所示:

public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    private MyView myView;
    private Button btn_clear;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample_my_view);
        myView = findViewById(R.id.myView);
        btn_clear = findViewById(R.id.btn_clear);

        //获取屏幕宽度和高度
        DisplayMetrics displayMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
        myView.init(displayMetrics.widthPixels,displayMetrics.heightPixels);

        btn_clear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myView.clear();
            }
        });
    }
}

2 基本图形变换-Matrix

@1 了解Matrix

Matrix在程序中可用于平移、旋转、缩放、倾斜,不仅可用于绘制中的图形,还可用于View组件中。Matrxi的常见方法也主要是针对平移、旋转、缩放、倾斜4个操作。

关于Matrix矩阵类,更多解读可查看官方文档:Android Matrix类详细解读

@2 Matrix实战

使用按键响应Matrix的常见操作,效果如下所示:

关于该程序,自定义View的关键代码如下所示:

public class MyView extends View {
    private static final String TAG = "MyView";
    private Matrix initMatrix = new Matrix();
    private Matrix matrix = new Matrix();
    private int h,w;
    public  Bitmap bitmap = null;

    public MyView(Context context) {
        super(context);
        init(context);
    }

    public MyView(Context context,AttributeSet attrs){
        super(context,attrs);
        init(context);
    }

    public MyView(Context context,AttributeSet attrs,int defStyleAttr){
        super(context,attrs,defStyleAttr);
        init(context);
    }

    public void setMatrix(Matrix matrix){
        this.matrix = matrix;
        invalidate();
    }

    private void init(Context context){
        initMatrix.reset();
        initMatrix.setScale(5.0f,5.0f);
        try {
            InputStream isImage = context.getAssets().open("test1.png");
            bitmap = BitmapFactory.decodeStream(isImage);
            w = bitmap.getWidth();
            h = bitmap.getHeight();
            bitmap = Bitmap.createBitmap(bitmap,0,0, w, h, initMatrix, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        //居中显示
        float centerStartX = (canvas.getWidth()-bitmap.getWidth())*1.0f/2;
        float centerStartY = (canvas.getHeight()-bitmap.getHeight())*1.0f/2;
        canvas.translate(centerStartX,centerStartY);
        //bitmap绘制
        canvas.drawBitmap(bitmap, matrix, null);
    }
}

在MainActivity中实现代码如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static String TAG = "MainActivity";
    private MyView myView;
    private Button btn_reset,btn_scale,btn_skew,btn_rotate,btn_translate;
    private Matrix matrix = new Matrix();
    private float scaleX=0.5f,scaleY=0.5f;
    private float rotate = 0.0f;
    private float skewX = 0.0f,skewY = 0.0f;
    private float translateX=0.0f,translateY=0.0f;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample_my_view);
        myView = findViewById(R.id.myView);
        btn_reset = findViewById(R.id.btn_reset);
        btn_scale = findViewById(R.id.btn_scale);
        btn_rotate = findViewById(R.id.btn_rotate);
        btn_translate = findViewById(R.id.btn_translate);
        btn_skew = findViewById(R.id.btn_skew);
        matrix.reset();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_rotate://旋转:持续,参数表度数,可持续叠加
                rotate+=10.0f;
                matrix.setRotate(rotate);
                break;
            case R.id.btn_scale://缩放范围0.5f-1.5f
                scaleX+=0.1f;
                scaleY+=0.1f;
                if(scaleX>=1.5f ||scaleY>=1.5f){
                    scaleX = 0.5f;
                    scaleY = 0.5f;
                }
                matrix.setScale(scaleX,scaleY);
                break;
            case R.id.btn_skew://错切范围:0.0f-1.0f
                skewX+=0.1f;
                if(skewX>1.0f || skewY>1.0f){
                    skewX = 0.0f;
                }
                matrix.setSkew(skewX,skewY);
                break;
            case R.id.btn_translate://平移范围:0.0f-200.f
                translateX+=10.0f;
                if(translateX>=200.0f ||translateY>=200.0f){
                    translateX = 0.0f;
                }
                matrix.setTranslate(translateX,translateY);
                break;
            default:
                matrix.reset();
                break;
        }
        myView.setMatrix(matrix);
    }
}

关于该程序,sample_my_view.xml 布局参考文件如下所示:

<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:visibility="visible">

        <Button
            android:id="@+id/btn_scale"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/scale" />

        <Button
            android:id="@+id/btn_rotate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/rotate" />

        <Button
            android:id="@+id/btn_skew"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/skew" />

        <Button
            android:id="@+id/btn_translate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/translate" />

        <Button
            android:id="@+id/btn_reset"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/reset" />
    </LinearLayout>

    <com.ags.myapplication.MyView
        android:id="@+id/myView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@mipmap/ic_launcher" />

Matrix变换是基本的图形变换,关于一些图形特效的效果,在下一节中详细讲解。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-02-06 13:56:29  更:2022-02-06 13:57:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:50:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码