动画与图形——画布与绘制

画布与绘制

这部分主要应用于游戏领域,学过H5游戏开发的旁友一定知道canvas的重要性,而安卓也提供了canvas的容器,当然也有对应的“画笔”——Paint。


绘制几何图形

那么如何在手机上绘制2D图形呢?

实际上在android SDK当中,并没有JavaGraphics2D的函数可以使用,而是使用android.graphics底下的类来绘制2D向量图。

  • 这个package提供了许多在手机上绘制图形的类与方法,Paint(Android.graphics.Paint)类则像是彩色铅笔,给予不同的调协,即可绘制不同颜色、不同种类效果的向量图形,最后统一呈现在在Canvas(画布)上。

👇将介绍如何运用Paint对象,在Cavas(画布)上绘制空心、实心及渐变色的几何图形等多种几何多形。

具体步骤

  1. 创建一个class文件MyView,继承View
  2. 构造方法传当前上下文
  3. 重写onDraw方法(在组件加载时调用)
  4. 修改MainActivity setContentView(new MyView(this));

MyView.class

package com.example.a4_22cavas;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
import android.view.View;

public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    //在组件加载时调用,参数即Canvas画布
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置背景为白色
        canvas.drawColor(Color.WHITE);
        //Paint即画笔
        Paint paint = new Paint();
        //去锯齿
        paint.setAntiAlias(true);
        //设置Paint颜色
        paint.setColor(Color.RED);

        /*********绘制空心*********/
        //设置Paint的风格 STROKE:空心
        paint.setStyle(Paint.Style.STROKE);
        //设置Paint外接宽度
        paint.setStrokeWidth(3);
        /*画一个空心圆形 */
        //参数:圆心x,圆心y,半径r,paint
        canvas.drawCircle(40, 40, 30, paint);
        /*画一个空心正方形*/
        //参数:left,top,right,buttom
        canvas.drawRect(10, 90, 70, 150, paint);
        /*画一个空心长方形*/
        canvas.drawRect(10, 170, 70, 200, paint);
        /*画一个空心椭圆形*/
        RectF re = new RectF(10, 220, 70, 250);
        canvas.drawOval(re, paint);
        /*画一个空心三角形*/
        Path path = new Path();
        path.moveTo(10, 330);
        path.lineTo(70, 330);
        path.lineTo(40, 270);
        path.close();
        canvas.drawPath(path, paint);
        /*画一个空心梯形*/
        Path path1 = new Path();
        path1.moveTo(10, 410);
        path1.lineTo(70, 410);
        path1.lineTo(55, 350);
        path1.lineTo(25, 350);
        path1.close();
        canvas.drawPath(path1, paint);


        /*********绘制实心********/
        //设置Paint的风格 FILL 实心
        paint.setStyle(Paint.Style.FILL);
        //设置Paint的颜色
        paint.setColor(Color.BLUE);
        /*画一个实心圆*/
        canvas.drawCircle(120, 40, 30, paint);
        /*画一个实心正方形*/
        canvas.drawRect(90, 90, 150, 150, paint);
        /*画一个长方形*/
        canvas.drawRect(90, 170, 150, 200, paint);
        /*画一个实心椭圆形*/
        RectF re1 = new RectF(90, 220, 150, 250);
        canvas.drawOval(re1, paint);
        /*画一个实心三角形*/
        Path path2 = new Path();
        path2.moveTo(90, 330);
        path2.lineTo(150, 330);
        path2.lineTo(120, 270);
        path2.close();
        canvas.drawPath(path2, paint);
        /*画一个实心梯形*/
        Path path3 = new Path();
        path3.moveTo(90, 410);
        path3.lineTo(150, 410);
        path3.lineTo(135, 350);
        path3.lineTo(105, 350);
        path3.close();
        canvas.drawPath(path3, paint);


        /*********绘制渐变色*********/
        //参数一二三四分别是起始终止xy位置
        // new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW}参与渐变颜色集合
        //随后的参数 new float[]{0,0.5f,1.0f} 每个颜色处于渐变的相对位置 为null则均匀分布
        //最后的参数 平铺方式 REPEAT重复 MIRROR镜像
        Shader mShader = new LinearGradient(0, 0, 100, 100, new int[]{
                Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW}, null,
                Shader.TileMode.MIRROR);
        paint.setShader(mShader);
        //画一个渐变色圆
        canvas.drawCircle(200, 40, 30, paint);
        //画一个渐变色正方形
        canvas.drawRect(170, 90, 230, 150, paint);
        //画一个渐变色长方形
        canvas.drawRect(170, 170, 230, 200, paint);
        //画一个渐变色椭圆形
        RectF re2 = new RectF(170, 220, 230, 250);
        canvas.drawOval(re2, paint);
        //画一个渐变色三角形
        Path path4 = new Path();
        path4.moveTo(170, 330);
        path4.lineTo(230, 330);
        path4.lineTo(200, 270);
        path4.close();
        canvas.drawPath(path4, paint);
        //画一个渐变色梯形
        Path path5 = new Path();
        path5.moveTo(170, 410);
        path5.lineTo(230, 410);
        path5.lineTo(215, 350);
        path5.lineTo(185, 350);
        path5.close();
        canvas.drawPath(path5, paint);
        //写字
        paint.setTextSize(24);
        canvas.drawText("圆形", 240, 50, paint);
        canvas.drawText("正方形", 240, 120, paint);
        canvas.drawText("长方形", 240, 190, paint);
        canvas.drawText("椭圆形", 240, 250, paint);
        canvas.drawText("三角形", 240, 320, paint);
        canvas.drawText("梯形", 240, 390, paint);
    }
}

MainActivity

package com.example.a4_22cavas;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
    }
}

效果如👇 有一点点小


绘制图像

不只是几何图形,图像也是可以绘制的,步骤和之前类似,就不赘述了。

canvas提供了drawBitmap方法,可以在画布上绘制你现有的图像资源。

自备图片素材至mipmap文件夹,也不要忘记修改MainActivity的setContentView

完整代码如下:

package com.example.a4_22cavas;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;

public class MyImageView extends View {
    public MyImageView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img1);
        canvas.drawBitmap(bitmap, 0, 0, paint);

    }
}

效果如👇,实际中应和其他绘制方法配合使用为佳 


SurfaceView

概述

  • SurfaceView是视图(View)的继承类。
  • 这个视图里内嵌了一个专门用于绘制的Surface(可以理解为一个独立的窗口)
  • 你可以控制这个Surface的格式和尺寸,SurfaceView控制这个Surface的绘制位置。
  • 可以直接从内存或者DMA等硬件接口取得图像数据,是个非常重要的绘图容器。
  • 传统View及其派生类的更新只能在UI线程,然而UI线程还同时处理其他交互逻辑,这就无法保证view更新的速度和帧率了,而SurfaceView可以用独立的线程来进行绘制,因此可以提供更高的帧率。
  • 例如游戏,摄像头取景等场景就比较适合用SurfaceView来实现。写入到Surface的内容可以被直接复制到显存从而显示出来,这使得显示速度会非常快。

简单案例 

setContentView同样需要改,别忘记

新建MySurfaceView类继承SurfaceView并实现SurfaceHolder.Callback

package com.example.a4_22cavas;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

//相当于独立屏幕,画在自己的屏幕上
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder holder;
    private MyThread thread;

    public MySurfaceView(Context context) {
        super(context);
        //获取holder
        holder = this.getHolder();
        //添加回调接口
        holder.addCallback(this);
    }

    //线程内部类
    class MyThread implements Runnable {
        private SurfaceHolder holder;
        //线程运行状态
        public boolean isRun;

        public MyThread(SurfaceHolder holder) {
            //传holder
            this.holder = holder;
            isRun = true;
        }

        @Override
        public void run() {
            int count = 0;
            Canvas canvas = null;
            while (isRun) {
                try {
                    //同步块
                    synchronized (holder) {
                        //锁定画布,一般锁定后就可以通过其返回的画布对象,在上面绘制了
                        canvas = holder.lockCanvas();
                        //背景颜色
                        canvas.drawColor(Color.WHITE);
                        //创建画笔
                        Paint paint = new Paint();
                        paint.setColor(Color.RED);
                        paint.setStyle(Paint.Style.FILL);
                        paint.setAntiAlias(true);
                        canvas.drawRect(10, 10, 100, 100, paint);
                        paint.setTextSize(24);
                        canvas.drawText("这是第" + (count++) + "秒", 10, 150, paint);
                        //睡眠时间为1秒
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //结束锁定画布,并且提交更改
                    holder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }


    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //屏幕一创建
        thread = new MyThread(holder);
        thread.isRun = true;
        new Thread(thread).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //屏幕改变
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //屏幕销毁
        thread.isRun = false;
    }
}

效果如👇 看秒数一直在刷新  

实现视频播放

创建一个新的Activty——VideoActivity

首先修改其布局文件

在布局上添加SurfaceView控件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".VideoActivity">

    <SurfaceView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        android:id="@+id/surfaceView"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/start"
        android:onClick="start"
        android:text="开始"
        app:layout_constraintTop_toBottomOf="@id/surfaceView"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pause"
        android:onClick="pause"
        android:text="暂停"
        app:layout_constraintTop_toBottomOf="@+id/start"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/end"
        android:onClick="end"
        android:text="结束"
        app:layout_constraintTop_toBottomOf="@id/pause"/>
</android.support.constraint.ConstraintLayout>

 

在VideoActivity注册对应组件,并实现SurfaceHolder.Callback

package com.example.a4_22cavas;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

import java.io.IOException;

public class VideoActivity extends AppCompatActivity implements SurfaceHolder.Callback {

    private SurfaceView surfaceView;
    private SurfaceHolder holder;
    private MediaPlayer mp;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video);
        surfaceView = findViewById(R.id.surfaceView);
        //获取surfaceholder,锁定画布之类的都不需要
        holder = surfaceView.getHolder();
        holder.addCallback(this);
        //设置分辨率,不设置为默认分辨率
        holder.setFixedSize(1280, 720);

    }


    public void start(View view) {
        mp.start();
    }

    public void pause(View view) {
        mp.pause();
    }

    public void end(View view) {
        mp.stop();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //画布创建的时候 播放
        mp = new MediaPlayer();
        mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
        //预备
        mp.setDisplay(holder);
        String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) + "/test.mp4";
        System.out.println(path);
        try {
            //设置播放视频源
            mp.setDataSource(path);
            mp.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mp != null) {
            if (mp.isPlaying()) {
                mp.stop();
                mp.release();
            }
        }
    }
}

遇到如👇错误

PlayEventLogger.uploadLog: Failed to connect to server: java.net.UnknownHostException: Unable to resolve host "play.googleapis.com": No address associated with hostname

以及

start called in state 64, mPlayer(0xa64fef60)

在配置文件加入以下权限

    <uses-permission android:name="android.permission.INTERNET"/>

 及文件读写权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

效果如👇

 


Draw 9-patch

与传统的png格式图片相比,9.png 格式图片在图片四周有一圈一个像素点组成的边沿,该边沿用于对图片的可扩展区和内容显示区进行定义。这种格式的图片在android环境下具有自适应调节大小的能力。

运行android SDK中tools路径下的draw9patch工具:

  • 给.9图片划线,指的是通过划线,决定图片的可拉伸区域和显示文本信息的区域。
  • 其中,上方和左 方的线是控制图片的可拉伸区域的,也就是说,上方的线控制图片横向可拉伸,左侧的线控制纵向可拉伸。
  • 下方的线和右侧的线控制图片的文本区域,也就是说,如果图片上有text,就会杷位置控制在下方和右侧的线围城的区域里。
  • 如果想制除划线,按住shift+鼠标左键,删除划线。

可以简单的理解为修图工具,新版Android Studio已经集成了Draw 9-patch工具,不需要再通过Draw 9-patch.bat去启动


 

原文链接:加载失败,请重新获取