自定义View全解,绘一个动态“飞机大战”
2016-12-08 本文已影响697人
番茄And鸡蛋
feiji.png
本文主要介绍自定义View绘图的使用,上面是一个可拖动的飞机,并且不断发射出子弹,完全使用自定义View绘图实现的动态效果,下面是一组动态效果。
飞机大战.gif下面我先介绍一下View中比较重要的几个方法跟参数:
- onFinishlnflate(): 这是一个回调方法,当应用从XML布局文件加载该组件并利用它来构造界面后,回调该方法。
- onMeasure(int,int):调用改方法来检测View组件以及所包含的所有子组件的大小。
- omLayout(boolean,int,int,int,int):当该组件需要分配其子组件的位置,大小时回调。
- onSizeChanged(int,int,int,int):组件的大小发生改变的时候回调。
- onDraw(Canvas):组件绘制内容的时候调用该方法进行绘制。
- onKeyDown(int,KeyEvent):当某个键被按下时触发。
- onKeyUp(int,KeyEvent):松开某个键时触发。
- onTrackballEvent(MotionEvent):发生轨迹球事件时触发。
- onTouchEvent(MotionEvent):发生触摸屏事件时触发。
- onFocusChanged(boolean gainFocus,int direction,Rect previouslyFocusedRect):该组件焦点发生改变时触发。
- onWindowFocusChanged(boolean):包含改组件的窗口失去或者得到焦点时触发。
- onAttachedToWindow():把该组件放入某个窗口时触发。
- onDetachedFromWindow():把该组件从某个窗口上分离时触发。
- onWindowVisibilityChanged(int):包含该组件的窗口的可见性发生改变时触发。
下面是本文的重点,绘制图形所涉及的重要几何图形绘制方法以及实例:
Paint p = new Paint();
p.setColor(Color.RED);// 设置红色
canvas.drawText("画圆:", 10, 20, p);// 画文本
canvas.drawCircle(60, 20, 10, p);// 小圆
p.setAntiAlias(true);// 设置画笔的锯齿效果。
canvas.drawCircle(120, 20, 20, p);// 大圆
canvas.drawText("画线及弧线:", 10, 60, p);
p.setColor(Color.GREEN);// 设置绿色
canvas.drawLine(60, 40, 100, 40, p);// 画线
canvas.drawLine(110, 40, 190, 80, p);// 斜线
//画笑脸弧线
p.setStyle(Paint.Style.STROKE);//设置空心
RectF oval1=new RectF(150,20,180,40);
canvas.drawArc(oval1, 180, 180, false, p);//小弧形
oval1.set(190, 20, 220, 40);
canvas.drawArc(oval1, 180, 180, false, p);//小弧形
oval1.set(160, 30, 210, 60);
canvas.drawArc(oval1, 0, 180, false, p);//小弧形
canvas.drawText("画矩形:", 10, 80, p);
p.setColor(Color.GRAY);// 设置灰色
p.setStyle(Paint.Style.FILL);//设置填满
canvas.drawRect(60, 60, 80, 80, p);// 正方形
canvas.drawRect(60, 90, 160, 100, p);// 长方形
canvas.drawText("画扇形和椭圆:", 10, 120, p);
/* 设置渐变色 这个正方形的颜色是改变的 */
Shader mShader = new LinearGradient(0, 0, 100, 100,
new int[] { Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW,
Color.LTGRAY }, null, Shader.TileMode.REPEAT); // 一个材质,打造出一个线性梯度沿著一条线。
p.setShader(mShader);
// p.setColor(Color.BLUE);
RectF oval2 = new RectF(60, 100, 200, 240);// 设置个新的长方形,扫描测量
canvas.drawArc(oval2, 200, 130, true, p);
// 画弧,第一个参数是RectF:该类是第二个参数是角度的开始,第三个参数是多少度,第四个参数是真的时候画扇形,是假的时候画弧线
//画椭圆,把oval改一下
oval2.set(210,100,250,130);
canvas.drawOval(oval2, p);
canvas.drawText("画三角形:", 10, 200, p);
// 绘制这个三角形,你可以绘制任意多边形
Path path = new Path();
path.moveTo(80, 200);// 此点为多边形的起点
path.lineTo(120, 250);
path.lineTo(80, 250);
path.close(); // 使这些点构成封闭的多边形
canvas.drawPath(path, p);
// 你可以绘制很多任意多边形,比如下面画六连形
p.reset();//重置
p.setColor(Color.LTGRAY);
p.setStyle(Paint.Style.STROKE);//设置空心
Path path1=new Path(); //Path类是连接路径
path1.moveTo(180, 200);
path1.lineTo(200, 200);
path1.lineTo(210, 210);
path1.lineTo(200, 220);
path1.lineTo(180, 220);
path1.lineTo(170, 210);
path1.close();//封闭
canvas.drawPath(path1, p);
//画圆角矩形
p.setStyle(Paint.Style.FILL);//充满
p.setColor(Color.LTGRAY);
p.setAntiAlias(true);// 设置画笔的锯齿效果
canvas.drawText("画圆角矩形:", 10, 260, p);
RectF oval3 = new RectF(80, 260, 200, 300);// 设置个新的长方形
canvas.drawRoundRect(oval3, 20, 15, p);//第二个参数是x半径,第三个参数是y半径
//画贝塞尔曲线
canvas.drawText("画贝塞尔曲线:", 10, 310, p);
p.reset();
p.setStyle(Paint.Style.STROKE);
p.setColor(Color.GREEN);
Path path2=new Path();
path2.moveTo(100, 320);//设置Path的起点
path2.quadTo(150, 310, 170, 400); //设置贝塞尔曲线的控制点坐标和终点坐标
canvas.drawPath(path2, p);//画出贝塞尔曲线
//画点
p.setStyle(Paint.Style.FILL);
canvas.drawText("画点:", 10, 390, p);
canvas.drawPoint(60, 390, p);//画一个点
canvas.drawPoints(new float[]{60,400,65,400,70,400}, p);//画多个点
//画图片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
canvas.drawBitmap(bitmap, 250,360, p);
除了以上绘制的方法,Canvas还提供了如下方法进行坐标变换:
- rotate(float degrees,float px,float py):对Canvas执行旋转变换。
- scale(float sx,float sy,float px,float py):对Canvas执行缩放变换。
- skew(float sx,float sy):对Canvas执行倾斜变换。
- trnslate(float dx,float dy):移动Canvas.向右移动dx距离(为负数相反方向移动),向下移动dy距离(为负数相反方向移动)。
Paint 代表了Canvas上的画笔、画刷、颜料等等,Paint类常用方法:
- setARGB(int a, int r, int g, int b) // 设置 Paint对象颜色,参数一为alpha透明值
- setAlpha(int a) // 设置alpha不透明度,范围为0~255
- setAntiAlias(boolean aa) // 是否抗锯齿
- setColor(int color) // 设置颜色,这里Android内部定义的有Color类包含了一些常见颜色定义
- setTextScaleX(float scaleX) // 设置文本缩放倍数,1.0f为原始
- setTextSize(float textSize) // 设置字体大小
- setUnderlineText(booleanunderlineText) // 设置下划线
- setStyle(Paint.Style style) //设置填充的风格
- setStrokeLayer(float radius,float dx,float dy,int color) //设置阴影。
- setStrokeWidth(float width) //设置画笔的笔触宽度
学习了上面的API知识,我们可以开始绘制自己需要的View跟效果了,下面是我模拟飞机射击,绘出来的一个View。主要还是注意算法的写入。
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
/**
* Created by Amin on 2016/12/1.
*/
public class ImageDragView extends View {
SheXian sheXian;
private float x1 = 500;
private float y1 = 1000;
private float ZHIDAN = 50;
private Bitmap bitmap;
public final ArrayList<SheXian> balls = new ArrayList<SheXian>(); float time2 = 0;
public ImageDragView(Context context) {
super(context);
iniData();
}
public ImageDragView(Context context, AttributeSet attrs) {
super(context, attrs);
iniData();
}
public ImageDragView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
iniData();
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
// 如果触碰事件不是按下、移动事件
if (event.getAction() != MotionEvent.ACTION_DOWN && event.getAction() != MotionEvent.ACTION_MOVE) {
return false;
}
if (Math.abs(x1 - event.getX()) < 100 && Math.abs(y1 - event.getY()) < 100) {
x1 = event.getX();
y1 = event.getY();
// if (balls.size() > 100) {
// balls.clear();
// }
// sheXian.setY1(y1);
// sheXian.setX1(x1);
// balls.add(sheXian);
}
if (Math.abs(getHeight() / 2 - x1) < 150 && Math.abs(getWidth() / 2 - y1) < 150) {
time2 = 0;
}
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawBitmap(bitmap, x1 - 72, y1 - 72, paint);
DrawLine(canvas, paint, time2, x1, y1);
paint.setColor(Color.BLUE);
canvas.drawCircle(getHeight() / 2, getWidth() / 2, 100, paint);
paint.setColor(Color.RED);
paint.setTextSize(40);
canvas.drawText("补充弹药", getHeight() / 2 - 70, getWidth() / 2, paint); drawMyLine(canvas, paint);
if (true) {
invalidate();
}
time2 = time2 + 20;
if (time2 >= 7500) {
time2 = 3750;
drawMyLine(canvas, paint);
}
}
private void drawMyLine(Canvas canvas, Paint paint) {
Log.i("mytag", "balls.size()=" + balls.size());
for (int i = 0; i < 50; i++) {
DrawLine(canvas, paint, time2 - 150 * i, x1, y1);
}
}
//
// private float X(int i) {
// Log.i("mytag", "i=" + i);
// if (balls.size() != 0) {
// Log.i("mytag", "balls.get(0).getX1()=" + balls.get(0).getX1());
// return balls.get(balls.size() - 1).getX1();
//
// } else {
// Log.i("mytag", "x1=" + x1);
// return x1;
// }
//
// }
//
// private float Y(int i) {
//
// if (balls.size() != 0) {
// Log.i("mytag", "balls.get(0).getX1()=" + balls.get(0).getY1());
// return balls.get(balls.size() - 1).getY1();
// } else {
// return y1;
// }
// }
private void DrawLine(Canvas canvas, Paint paint, float time, float x2, float y2) {
if (time > 0) {
canvas.drawLine(x2 + X3(time * (float) Math.sqrt(2) / 2), y2 + Y3(time * (float) Math.sqrt(2) / 2),
x2 + X3(time * (float) Math.sqrt(2) / 2 + ZHIDAN), y2 + Y3(time * (float) Math.sqrt(2) / 2 + ZHIDAN), paint);
canvas.drawLine(x2 - time * (float) Math.sqrt(2) / 2, y2 - time * (float) Math.sqrt(2) / 2,
x2 - time * (float) Math.sqrt(2) / 2 - ZHIDAN, y2 - time * (float) Math.sqrt(2) / 2 - ZHIDAN, paint);
canvas.drawLine(x2 + time * (float) Math.sqrt(2) / 2, y2 - time * (float) Math.sqrt(2) / 2,
x2 + time * (float) Math.sqrt(2) / 2 + ZHIDAN, y2 - time * (float) Math.sqrt(2) / 2 - ZHIDAN, paint);
canvas.drawLine(x2 - X3(time * (float) Math.sqrt(2) / 2), y2 + Y3(time * (float) Math.sqrt(2) / 2),
x2 - X3(time * (float) Math.sqrt(2) / 2 - ZHIDAN), y2 + Y3(time * (float) Math.sqrt(2) / 2 - ZHIDAN), paint);
canvas.drawLine(x2 - time, y2, x2 - time - ZHIDAN, y2, paint);
canvas.drawLine(x2, y2 - time, x2, y2 - time - ZHIDAN, paint);
canvas.drawLine(x2 + time, y2, x2 + time + ZHIDAN, y2, paint);
// canvas.drawLine(x1, y1 + time, x1, y1 + time + 70, paint);
}
}
private float X3(float x) {
if (x < 300) {
return x;
} else {
return 300;
}
}
private float Y3(float y) {
if (y < 300) {
return y;
} else {
return -y + 620;
}
}
private void iniData() {
Resources res = getResources();
bitmap = BitmapFactory.decodeResource(res, R.drawable.feiji2);
sheXian = new SheXian();
}
}
代码中我添加的监听事件是onTouchEvent,并且判断了手势的滑动,排除了非按下跟移动事件。为了实现拖动效果,在拖动的范围必须在飞机的正负100PX之内。当X1 Y1发生改变时,界面也开始重绘。因为我在绘制方法内写入一个死循环,不停地刷新界面绘制调用invalidate()方法。而子弹的射出,是循环绘制的50发子弹,并写入算法,不停的改变x y 的坐标,成规律型增长。就实现了移动效果。在iniData()方法中,获取资源图片,就是飞机的图片格式转换为bitmap位图。另外就是我直接手机测试完成,分辨率为1080*1920的,在代码中并没有进行分辨率的适配,直接用的PX像素单位,不同的手机运行起来肯定有差异了,注意更改适配。
完全使用刷新重绘完成的View,所以动画的效果更改不是那么完善,后面我会用ObjectAnimator来介绍自定义View跟自定义的属性动画所结合,更灵活的实现子弹独立化的移动。本文就介绍到这里,不懂的地方和不足之处请留言,谢谢支持。