画布与绘制
这部分主要应用于游戏领域,学过H5游戏开发的旁友一定知道canvas的重要性,而安卓也提供了canvas的容器,当然也有对应的“画笔”——Paint。
绘制几何图形
那么如何在手机上绘制2D图形呢?
实际上在android SDK当中,并没有JavaGraphics2D的函数可以使用,而是使用android.graphics底下的类来绘制2D向量图。
- 这个package提供了许多在手机上绘制图形的类与方法,Paint(Android.graphics.Paint)类则像是彩色铅笔,给予不同的调协,即可绘制不同颜色、不同种类效果的向量图形,最后统一呈现在在Canvas(画布)上。
👇将介绍如何运用Paint对象,在Cavas(画布)上绘制空心、实心及渐变色的几何图形等多种几何多形。
具体步骤
- 创建一个class文件MyView,继承View
- 构造方法传当前上下文
- 重写onDraw方法(在组件加载时调用)
- 修改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去启动