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变换是基本的图形变换,关于一些图形特效的效果,在下一节中详细讲解。
|