一Bitmap和BitmapFactory
二Android绘图基础
三Path类
PathText
采用双缓冲实现画图板
弹球游戏
使用drawBitmapMesh扭曲图像
使用Shader填充图形
Bitmap提供下面静态方法来创建Bitmap对象:
1. createBitmap(Bitmap source,int x ,int y,int width,int height).
2. createScaledBitmap(Bitmap src,int dstWidth,int dstHeight,boolean filter).
3. createBitmap(int width,int height,Bitmap.Config config)
4. createBitmap(Bitmap source,int x,int y,int width,int height,Matrix m,boolean filter)
BitmapFactory提供如下方法从不同的数据源来解析创建Bitmap对象。
- decodeByteArray(byte[] data,int offset,int length);
- decodeFile(String pathName);
- decodeFileDescriptor(FileDescriptor fd);
- decodeResource(Resource res,int id);
- decodeStream(inputStream is);
Android为Bitmap提供下面两个方法来判断它是否回收,以及强制Bitmap回收自己。
- boolean isRecycled();
- void recycle();
public class MainActivity extends Activity
{
String[] images = null;
AssetManager assets = null;
int currentImg = 0;
ImageView image;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
image = (ImageView) findViewById(R.id.image);
try
{
assets = getAssets();
// 获取/assets/目录下所有文件
images = assets.list("");
}
catch (IOException e)
{
e.printStackTrace();
}
// 获取next按钮
final Button next = (Button) findViewById(R.id.next);
// 为next按钮绑定事件监听器,该监听器将会查看下一张图片
next.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View sources)
{
// 如果发生数组越界
if (currentImg >= images.length)
{
currentImg = 0;
}
// 找到下一个图片文件
while (!images[currentImg].endsWith(".png")
&& !images[currentImg].endsWith(".jpg")
&& !images[currentImg].endsWith(".gif"))
{
currentImg++;
// 如果已发生数组越界
if (currentImg >= images.length)
{
currentImg = 0;
}
}
InputStream assetFile = null;
try
{
// 打开指定资源对应的输入流
assetFile = assets.open(images[currentImg++]);
}
catch (IOException e)
{
e.printStackTrace();
}
BitmapDrawable bitmapDrawable = (BitmapDrawable) image
.getDrawable();
// 如果图片还未回收,先强制回收该图片
if (bitmapDrawable != null
&& !bitmapDrawable.getBitmap().isRecycled()) // ①
{
bitmapDrawable.getBitmap().recycle();
}
// 改变ImageView显示的图片
image.setImageBitmap(BitmapFactory
.decodeStream(assetFile)); // ②
}
});
}
}
方法 | 简要说明 |
---|---|
drawCircle() | 画圆 |
drawPath(Path path,Paint paint) | 沿着指定Path绘制任意形状 |
drawRect(RectF rect, Paint paint) | 绘制区域,参数一为RectF一个区域 |
drawPath(Path path, Paint paint) | 绘制一个路径,参数一为Path路径对象 |
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) | 贴图,参数一就是我们常规的Bitmap对象,参数二是源区域(这里是bitmap),参数三是目标区域(应该在canvas的位置和大小),参数四是Paint画刷对象,因为用到了缩放和拉伸的可能,当原始Rect不等于目标Rect时性能将会有大幅损失。 |
drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) | 画线,参数一起始点的x轴位置,参数二起始点的y轴位置,参数三终点的x轴水平位置,参数四y轴垂直位置,最后一个参数为Paint 画刷对象。 |
drawPoint(float x, float y, Paint paint) | 画点,参数一水平x轴,参数二垂直y轴,第三个参数为Paint对象。 |
drawText(String text, float x, floaty, Paint paint) | 渲染文本,Canvas类除了上面的还可以描绘文字,参数一是String类型的文本,参数二x轴,参数三y轴,参数四是Paint对象。 |
drawOval(RectF oval, Paint paint) | 画椭圆,参数一是扫描区域,参数二为paint对象; |
drawCircle(float cx, float cy, float radius,Paint paint) | 绘制圆,参数一是中心点的x轴,参数二是中心点的y轴,参数三是半径,参数四是paint对象; |
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) | 画弧,参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,参数二是起始角(度)在电弧的开始,参数三扫描角(度)开始顺时针测量的,参数四是如果这是真的话,包括椭圆中心的电弧,并关闭它,如果它是假这将是一个弧线,参数五是Paint对象; |
还有如下方法进行坐标变换
方法 | 简要说明 |
---|---|
rotate(float degrees,float px,float py) | 对Canvas执行旋转变换 |
scale(float sx,float sy,float px,float py) | 对Canvas执行缩放变换 |
skew(float sx,float sy) | 对Canvas执行倾斜变换 |
trenslate(float dx,float dy) | 移动Canvas.向dx距离向下移动dy距离 |
public class MyView extends View
{
public MyView(Context context, AttributeSet set)
{
super(context, set);
}
@Override
// 重写该方法,进行绘图
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
// 把整张画布绘制成白色
canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
// 去锯齿
paint.setAntiAlias(true);
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(4);
int viewWidth = this.getWidth();
// 绘制圆形
canvas.drawCircle(viewWidth / 10 + 10, viewWidth / 10 + 10
, viewWidth / 10, paint);
// 绘制正方形
canvas.drawRect(10 , viewWidth / 5 + 20 , viewWidth / 5 + 10
, viewWidth * 2 / 5 + 20 , paint);
// 绘制矩形
canvas.drawRect(10, viewWidth * 2 / 5 + 30, viewWidth / 5 + 10
, viewWidth / 2 + 30, paint);
RectF re1 = new RectF(10, viewWidth / 2 + 40
, 10 + viewWidth / 5 ,viewWidth * 3 / 5 + 40);
// 绘制圆角矩形
canvas.drawRoundRect(re1, 15, 15, paint);
RectF re11 = new RectF(10, viewWidth * 3 / 5 + 50
, 10 + viewWidth / 5 ,viewWidth * 7 / 10 + 50);
// 绘制椭圆
canvas.drawOval(re11, paint);
// 定义一个Path对象,封闭成一个三角形
Path path1 = new Path();
path1.moveTo(10, viewWidth * 9 / 10 + 60);
path1.lineTo(viewWidth / 5 + 10, viewWidth * 9 / 10 + 60);
path1.lineTo(viewWidth / 10 + 10, viewWidth * 7 / 10 + 60);
path1.close();
// 根据Path进行绘制,绘制三角形
canvas.drawPath(path1, paint);
// 定义一个Path对象,封闭成一个五角形
Path path2 = new Path();
path2.moveTo(10 + viewWidth / 15, viewWidth * 9 / 10 + 70);
path2.lineTo(10 + viewWidth * 2 / 15, viewWidth * 9 / 10 + 70);
path2.lineTo(10 + viewWidth / 5, viewWidth + 70);
path2.lineTo(10 + viewWidth / 10, viewWidth * 11/10 + 70);
path2.lineTo(10 , viewWidth + 70);
path2.close();
// 根据Path进行绘制,绘制五角形
canvas.drawPath(path2, paint);
// ----------设置填充风格后绘制----------
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
// 绘制圆形
canvas.drawCircle(viewWidth * 3 / 10 + 20, viewWidth / 10 + 10
, viewWidth / 10, paint);
// 绘制正方形
canvas.drawRect(viewWidth / 5 + 20 , viewWidth / 5 + 20
, viewWidth * 2 / 5 + 20 , viewWidth * 2 / 5 + 20 , paint);
// 绘制矩形
canvas.drawRect(viewWidth / 5 + 20, viewWidth * 2 / 5 + 30
, viewWidth * 2 / 5 + 20 , viewWidth / 2 + 30, paint);
RectF re2 = new RectF(viewWidth / 5 + 20, viewWidth / 2 + 40
, 20 + viewWidth * 2 / 5 ,viewWidth * 3 / 5 + 40);
// 绘制圆角矩形
canvas.drawRoundRect(re2, 15, 15, paint);
RectF re21 = new RectF(20 + viewWidth / 5, viewWidth * 3 / 5 + 50
, 20 + viewWidth * 2 / 5 ,viewWidth * 7 / 10 + 50);
// 绘制椭圆
canvas.drawOval(re21, paint);
// 定义一个Path对象,封闭成一个三角形
Path path3 = new Path();
path3.moveTo(20 + viewWidth / 5, viewWidth * 9 / 10 + 60);
path3.lineTo(viewWidth * 2 / 5 + 20, viewWidth * 9 / 10 + 60);
path3.lineTo(viewWidth * 3 / 10 + 20, viewWidth * 7 / 10 + 60);
path3.close();
// 根据Path进行绘制,绘制三角形
canvas.drawPath(path3, paint);
// 定义一个Path对象,封闭成一个五角形
Path path4 = new Path();
path4.moveTo(20 + viewWidth *4 / 15, viewWidth * 9 / 10 + 70);
path4.lineTo(20 + viewWidth / 3, viewWidth * 9 / 10 + 70);
path4.lineTo(20 + viewWidth * 2 / 5, viewWidth + 70);
path4.lineTo(20 + viewWidth * 3 / 10, viewWidth * 11/10 + 70);
path4.lineTo(20 + viewWidth / 5 , viewWidth + 70);
path4.close();
// 根据Path进行绘制,绘制五角形
canvas.drawPath(path4, paint);
// ----------设置渐变器后绘制----------
// 为Paint设置渐变器
Shader mShader = new LinearGradient(0, 0, 40, 60
, new int[] {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW }
, null , Shader.TileMode.REPEAT);
paint.setShader(mShader);
//设置阴影
paint.setShadowLayer(25 , 20 , 20 , Color.GRAY);
// 绘制圆形
canvas.drawCircle(viewWidth / 2 + 30, viewWidth / 10 + 10
, viewWidth / 10, paint);
// 绘制正方形
canvas.drawRect(viewWidth * 2 / 5 + 30 , viewWidth / 5 + 20
, viewWidth * 3 / 5 + 30 , viewWidth * 2 / 5 + 20 , paint);
// 绘制矩形
canvas.drawRect(viewWidth * 2 / 5 + 30, viewWidth * 2 / 5 + 30
, viewWidth * 3 / 5 + 30 , viewWidth / 2 + 30, paint);
RectF re3 = new RectF(viewWidth * 2 / 5 + 30, viewWidth / 2 + 40
, 30 + viewWidth * 3 / 5 ,viewWidth * 3 / 5 + 40);
// 绘制圆角矩形
canvas.drawRoundRect(re3, 15, 15, paint);
RectF re31 = new RectF(30 + viewWidth *2 / 5, viewWidth * 3 / 5 + 50
, 30 + viewWidth * 3 / 5 ,viewWidth * 7 / 10 + 50);
// 绘制椭圆
canvas.drawOval(re31, paint);
// 定义一个Path对象,封闭成一个三角形
Path path5 = new Path();
path5.moveTo(30 + viewWidth * 2/ 5, viewWidth * 9 / 10 + 60);
path5.lineTo(viewWidth * 3 / 5 + 30, viewWidth * 9 / 10 + 60);
path5.lineTo(viewWidth / 2 + 30, viewWidth * 7 / 10 + 60);
path5.close();
// 根据Path进行绘制,绘制三角形
canvas.drawPath(path5, paint);
// 定义一个Path对象,封闭成一个五角形
Path path6 = new Path();
path6.moveTo(30 + viewWidth * 7 / 15, viewWidth * 9 / 10 + 70);
path6.lineTo(30 + viewWidth * 8 / 15, viewWidth * 9 / 10 + 70);
path6.lineTo(30 + viewWidth * 3 / 5, viewWidth + 70);
path6.lineTo(30 + viewWidth / 2, viewWidth * 11/10 + 70);
path6.lineTo(30 + viewWidth * 2 / 5 , viewWidth + 70);
path6.close();
// 根据Path进行绘制,绘制五角形
canvas.drawPath(path6, paint);
// ----------设置字符大小后绘制----------
paint.setTextSize(48);
paint.setShader(null);
// 绘制7个字符串
canvas.drawText(getResources().getString(R.string.circle)
, 60 + viewWidth * 3 / 5, viewWidth / 10 + 10, paint);
canvas.drawText(getResources().getString(R.string.square)
, 60 + viewWidth * 3 / 5, viewWidth * 3 / 10 + 20, paint);
canvas.drawText(getResources().getString(R.string.rect)
, 60 + viewWidth * 3 / 5, viewWidth * 1 / 2 + 20, paint);
canvas.drawText(getResources().getString(R.string.round_rect)
, 60 + viewWidth * 3 / 5, viewWidth * 3 / 5 + 30, paint);
canvas.drawText(getResources().getString(R.string.oval)
, 60 + viewWidth * 3 / 5, viewWidth * 7 / 10 + 30, paint);
canvas.drawText(getResources().getString(R.string.triangle)
, 60 + viewWidth * 3 / 5, viewWidth * 9 / 10 + 30, paint);
canvas.drawText(getResources().getString(R.string.pentagon)
, 60 + viewWidth * 3 / 5, viewWidth * 11 / 10 + 30, paint);
}
}
Android还为路径绘制提供PathEffect来定义绘制效果。PathEffect还有如下子类:
1. ComposePathEffect
2. CornerPathEffect
3. DashPathEffect
4. DiscretePathEffect
5. PathDashPathEffect
6. SumPathEffect
public class MainActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
}
class MyView extends View
{
float phase;
PathEffect[] effects = new PathEffect[7];
int[] colors;
private Paint paint;
Path path;
public MyView(Context context)
{
super(context);
paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(4);
// 创建并初始化Path
path = new Path();
path.moveTo(0, 0);
for (int i = 1; i <= 40; i++)
{
// 生成40个点,随机生成它们的Y坐标,并将它们连成一条Path
path.lineTo(i * 20, (float) Math.random() * 60);
}
// 初始化7个颜色
colors = new int[] { Color.BLACK, Color.BLUE, Color.CYAN,
Color.GREEN, Color.MAGENTA, Color.RED, Color.YELLOW };
}
@Override
protected void onDraw(Canvas canvas)
{
// 将背景填充成白色
canvas.drawColor(Color.WHITE);
// -----------下面开始初始化7种路径效果----------
// 不使用路径效果
effects[0] = null;
// 使用CornerPathEffect路径效果
effects[1] = new CornerPathEffect(10);
// 初始化DiscretePathEffect
effects[2] = new DiscretePathEffect(3.0f, 5.0f);
// 初始化DashPathEffect
effects[3] = new DashPathEffect(new float[] { 20, 10, 5, 10 },
phase);
// 初始化PathDashPathEffect
Path p = new Path();
p.addRect(0, 0, 8, 8, Path.Direction.CCW);
effects[4] = new PathDashPathEffect(p, 12, phase,
PathDashPathEffect.Style.ROTATE);
// 初始化ComposePathEffect
effects[5] = new ComposePathEffect(effects[2], effects[4]);
effects[6] = new SumPathEffect(effects[4], effects[3]);
// 将画布移动到(8、8)处开始绘制
canvas.translate(8, 8);
// 依次使用7种不同路径效果、7种不同的颜色来绘制路径
for (int i = 0; i < effects.length; i++)
{
paint.setPathEffect(effects[i]);
paint.setColor(colors[i]);
canvas.drawPath(path, paint);
canvas.translate(0, 60);
}
// 改变phase值,形成动画效果
phase += 1;
invalidate();
}
}
}
public class MainActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(new TextView(this));
}
class TextView extends View
{
final String DRAW_STR = "aserbao";
Path[] paths = new Path[3];
Paint paint;
public TextView(Context context)
{
super(context);
paths[0] = new Path();
paths[0].moveTo(0, 0);
for (int i = 1; i <= 20; i++)
{
// 生成20个点,随机生成它们的Y坐标,并将它们连成一条Path
paths[0].lineTo(i * 30, (float) Math.random() * 30);
}
paths[1] = new Path();
RectF rectF = new RectF(0, 0, 600, 360);
paths[1].addOval(rectF, Path.Direction.CCW);
paths[2] = new Path();
paths[2].addArc(rectF, 60, 180);
// 初始化画笔
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.CYAN);
paint.setStrokeWidth(1);
}
@Override
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.WHITE);
canvas.translate(40, 40);
// 设置从右边开始绘制(右对齐)
paint.setTextAlign(Paint.Align.RIGHT);
paint.setTextSize(20);
// 绘制路径
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(paths[0], paint);
paint.setTextSize(40);
// 沿着路径绘制一段文本
paint.setStyle(Paint.Style.FILL);
canvas.drawTextOnPath(DRAW_STR, paths[0], -8, 20, paint);
// 对Canvas进行坐标变换:画布下移60
canvas.translate(0, 60);
// 绘制路径
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(paths[1], paint);
// 沿着路径绘制一段文本
paint.setStyle(Paint.Style.FILL);
canvas.drawTextOnPath(DRAW_STR, paths[1], -20, 20, paint);
// 对Canvas进行坐标变换: 画布下移360
canvas.translate(0, 360);
// 绘制路径
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(paths[2], paint);
// 沿着路径绘制一段文本
paint.setStyle(Paint.Style.FILL);
canvas.drawTextOnPath(DRAW_STR, paths[2], -10, 20, paint);
}
}
}
public class DrawView extends View
{
// 定义记录前一个拖动事件发生点的坐标
float preX;
float preY;
private Path path;
public Paint paint = null;
// 定义一个内存中的图片,该图片将作为缓冲区
Bitmap cacheBitmap = null;
// 定义cacheBitmap上的Canvas对象
Canvas cacheCanvas = null;
public DrawView(Context context, int width , int height)
{
super(context);
// 创建一个与该View相同大小的缓存区
cacheBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
cacheCanvas = new Canvas();
path = new Path();
// 设置cacheCanvas将会绘制到内存中的cacheBitmap上
cacheCanvas.setBitmap(cacheBitmap);
// 设置画笔的颜色
paint = new Paint(Paint.DITHER_FLAG);
paint.setColor(Color.RED);
// 设置画笔风格
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1);
// 反锯齿
paint.setAntiAlias(true);
paint.setDither(true);
}
@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.quadTo(preX, preY, x, y);
preX = x;
preY = y;
break;
case MotionEvent.ACTION_UP:
cacheCanvas.drawPath(path, paint); // ①
path.reset();
break;
}
invalidate();
// 返回true表明处理方法已经处理该事件
return true;
}
@Override
public void onDraw(Canvas canvas)
{
Paint bmpPaint = new Paint();
// 将cacheBitmap绘制到该View组件上
canvas.drawBitmap(cacheBitmap, 0, 0, bmpPaint); // ②
// 沿着path绘制
canvas.drawPath(path, paint);
}
}
public class MainActivity extends Activity
{
EmbossMaskFilter emboss;
BlurMaskFilter blur;
DrawView drawView;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
LinearLayout line = new LinearLayout(this);
DisplayMetrics displayMetrics = new DisplayMetrics();
// 获取创建的宽度和高度
getWindowManager().getDefaultDisplay()
.getRealMetrics(displayMetrics);
// 创建一个DrawView,该DrawView的宽度、高度与该Activity保持相同
drawView = new DrawView(this, displayMetrics.widthPixels
, displayMetrics.heightPixels);
line.addView(drawView);
setContentView(line);
emboss = new EmbossMaskFilter(new float[]
{ 1.5f, 1.5f, 1.5f }, 0.6f, 6, 4.2f);
blur = new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL);
}
@Override
// 负责创建选项菜单
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflator = new MenuInflater(this);
// 装载R.menu.my_menu对应的菜单,并添加到menu中
inflator.inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
// 菜单项被单击后的回调方法
public boolean onOptionsItemSelected(MenuItem mi)
{
// 判断单击的是哪个菜单项,并有针对性地作出响应
switch (mi.getItemId())
{
case R.id.red:
drawView.paint.setColor(Color.RED);
mi.setChecked(true);
break;
case R.id.green:
drawView.paint.setColor(Color.GREEN);
mi.setChecked(true);
break;
case R.id.blue:
drawView.paint.setColor(Color.BLUE);
mi.setChecked(true);
break;
case R.id.width_1:
drawView.paint.setStrokeWidth(1);
break;
case R.id.width_3:
drawView.paint.setStrokeWidth(3);
break;
case R.id.width_5:
drawView.paint.setStrokeWidth(5);
break;
case R.id.blur:
drawView.paint.setMaskFilter(blur);
break;
case R.id.emboss:
drawView.paint.setMaskFilter(emboss);
break;
}
return true;
}
}
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/color">
<menu>
<group android:checkableBehavior="single">
<item android:id="@+id/red"
android:title="@string/color_red"/>
<item android:id="@+id/green"
android:title="@string/color_green"/>
<item android:id="@+id/blue"
android:title="@string/color_blue"/>
group>
menu>
item>
<item android:title="@string/width">
<menu>
<group>
<item android:id="@+id/width_1"
android:title="@string/width_1"/>
<item android:id="@+id/width_3"
android:title="@string/width_3"/>
<item android:id="@+id/width_5"
android:title="@string/width_5"/>
group>
menu>
item>
<item android:id="@+id/blur" android:title="@string/blur"/>
<item android:id="@+id/emboss" android:title="@string/emboss"/>
menu>
public class MainActivity extends Activity
{
// 桌面的宽度
private int tableWidth;
// 桌面的高度
private int tableHeight;
// 球拍的垂直位置
private int racketY;
// 下面定义球拍的高度和宽度
private final int RACKET_HEIGHT = 30;
private final int RACKET_WIDTH = 90;
// 小球的大小
private final int BALL_SIZE = 16;
// 小球纵向的运行速度
private int ySpeed = 15;
Random rand = new Random();
// 返回一个-0.5~0.5的比率,用于控制小球的运行方向
private double xyRate = rand.nextDouble() - 0.5;
// 小球横向的运行速度
private int xSpeed = (int) (ySpeed * xyRate * 2);
// ballX和ballY代表小球的坐标
private int ballX = rand.nextInt(200) + 20;
private int ballY = rand.nextInt(10) + 20;
// racketX代表球拍的水平位置
private int racketX = rand.nextInt(200);
// 游戏是否结束的旗标
private boolean isLose = false;
private GameView contentView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 去掉窗口标题
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 全屏显示
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 创建GameView组件
final GameView gameView = new GameView(this);
setContentView(gameView);
// 获取窗口管理器
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
// 获得屏幕宽和高
tableWidth = metrics.widthPixels;
tableHeight = metrics.heightPixels;
racketY = tableHeight - 80;
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
gameView.invalidate();
}
}
};
gameView.setOnKeyListener(new OnKeyListener() // ②
{
@Override
public boolean onKey(View source, int keyCode, KeyEvent event) {
// 获取由哪个键触发的事件
switch (event.getKeyCode()) {
// 控制挡板左移
case KeyEvent.KEYCODE_A:
if (racketX > 0) racketX -= 10;
break;
// 控制挡板右移
case KeyEvent.KEYCODE_D:
if (racketX < tableWidth - RACKET_WIDTH) racketX += 10;
break;
}
// 通知gameView组件重绘
gameView.invalidate();
return true;
}
});
final Timer timer = new Timer();
timer.schedule(new TimerTask() // ①
{
@Override
public void run() {
// 如果小球碰到左边边框
if (ballX <= 0 || ballX >= tableWidth - BALL_SIZE) {
xSpeed = -xSpeed;
}
// 如果小球高度超出了球拍位置,且横向不在球拍范围之内,游戏结束
if (ballY >= racketY - BALL_SIZE
&& (ballX < racketX || ballX > racketX
+ RACKET_WIDTH)) {
timer.cancel();
// 设置游戏是否结束的旗标为true
isLose = true;
}
// 如果小球位于球拍之内,且到达球拍位置,小球反弹
else if (ballY <= 0
|| (ballY >= racketY - BALL_SIZE
&& ballX > racketX && ballX <= racketX
+ RACKET_WIDTH)) {
ySpeed = -ySpeed;
}
// 小球坐标增加
ballY += ySpeed;
ballX += xSpeed;
// 发送消息,通知系统重绘组件
handler.sendEmptyMessage(0x123);
}
}, 0, 100);
}
class GameView extends View
{
Paint paint = new Paint();
public GameView(Context context)
{
super(context);
setFocusable(true);
}
// 重写View的onDraw方法,实现绘画
public void onDraw(Canvas canvas)
{
paint.setStyle(Paint.Style.FILL);
// 设置去锯齿
paint.setAntiAlias(true);
// 如果游戏已经结束
if (isLose)
{
paint.setColor(Color.RED);
paint.setTextSize(40);
canvas.drawText("游戏已结束", tableWidth / 2 - 100, 200, paint);
}
// 如果游戏还未结束
else
{
// 设置颜色,并绘制小球
paint.setColor(Color.rgb(255, 0, 0));
canvas.drawCircle(ballX, ballY, BALL_SIZE, paint);
// 设置颜色,并绘制球拍
paint.setColor(Color.rgb(80, 80, 200));
canvas.drawRect(racketX, racketY, racketX + RACKET_WIDTH,
racketY + RACKET_HEIGHT, paint);
}
}
}
}
1. 获取Matrix对象。
2. 调用Matrix的平移,旋转,缩放,倾斜等。
3. 将程序对Matrix所做的变换应用的指定图形或组件。
public class MyView extends View
{
// 初始的图片资源
private Bitmap bitmap;
// Matrix 实例
private Matrix matrix = new Matrix();
// 设置倾斜度
private float sx = 0.0f;
// 位图宽和高
private int width, height;
// 缩放比例
private float scale = 1.0f;
// 判断缩放还是旋转
private boolean isScale = false;
public MyView(Context context , AttributeSet set)
{
super(context , set);
// 获得位图
bitmap = ((BitmapDrawable) context.getResources().getDrawable(
R.drawable.a)).getBitmap();
// 获得位图宽
width = bitmap.getWidth();
// 获得位图高
height = bitmap.getHeight();
// 使当前视图获得焦点
this.setFocusable(true);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
// 重置Matrix
matrix.reset();
if (!isScale)
{
// 旋转Matrix
matrix.setSkew(sx, 0);
}
else
{
// 缩放Matrix
matrix.setScale(scale, scale);
}
// 根据原始位图和Matrix创建新图片
Bitmap bitmap2 = Bitmap.createBitmap(bitmap, 0, 0, width, height,
matrix, true);
// 绘制新位图
canvas.drawBitmap(bitmap2, matrix, null);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
switch(keyCode)
{
// 向左倾斜
case KeyEvent.KEYCODE_A:
isScale = false;
sx += 0.1;
postInvalidate();
break;
// 向右倾斜
case KeyEvent.KEYCODE_D:
isScale = false;
sx -= 0.1;
postInvalidate();
break;
// 放大
case KeyEvent.KEYCODE_W:
isScale = true;
if (scale < 2.0)
scale += 0.1;
postInvalidate();
break;
// 缩小
case KeyEvent.KEYCODE_S:
isScale = true;
if (scale > 0.5)
scale -= 0.1;
postInvalidate();
break;
}
return super.onKeyDown(keyCode, event);
}
}
可以使用此方法在Android应用中开发除“水波荡漾”“风吹旗帜”等扭曲效果
代码:
public class MainActivity extends Activity
{
private Bitmap bitmap;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(new MyView(this, R.drawable.jinta));
}
private class MyView extends View
{
// 定义两个常量,这两个常量指定该图片横向、纵向上都被划分为20格
private final int WIDTH = 20;
private final int HEIGHT = 20;
// 记录该图片上包含441个顶点
private final int COUNT = (WIDTH + 1) * (HEIGHT + 1);
// 定义一个数组,保存Bitmap上的21 * 21个点的坐标
private final float[] verts = new float[COUNT * 2];
// 定义一个数组,记录Bitmap上的21 * 21个点经过扭曲后的坐标
// 对图片进行扭曲的关键就是修改该数组里元素的值
private final float[] orig = new float[COUNT * 2];
public MyView(Context context, int drawableId)
{
super(context);
setFocusable(true);
// 根据指定资源加载图片
bitmap = BitmapFactory.decodeResource(getResources()
, drawableId);
// 获取图片宽度、高度
float bitmapWidth = bitmap.getWidth();
float bitmapHeight = bitmap.getHeight();
int index = 0;
for (int y = 0; y <= HEIGHT; y++)
{
float fy = bitmapHeight * y / HEIGHT;
for (int x = 0; x <= WIDTH; x++)
{
float fx = bitmapWidth * x / WIDTH;
// 初始化orig、verts数组。 初始化后,orig、verts
// 两个数组均匀地保存了21 * 21个点的x,y坐标
orig[index * 2 + 0] = verts[index * 2 + 0] = fx;
orig[index * 2 + 1] = verts[index * 2 + 1] = fy;
index += 1;
}
}
// 设置背景色
setBackgroundColor(Color.WHITE);
}
@Override
protected void onDraw(Canvas canvas)
{
//对bitmap按verts数组进行扭曲
//从第一个点(由第5个参数0控制)开始扭曲
canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT, verts
, 0, null, 0,null);
}
// 工具方法,用于根据触摸事件的位置计算verts数组里各元素的值
private void warp(float cx, float cy)
{
for (int i = 0; i < COUNT * 2; i += 2)
{
float dx = cx - orig[i + 0];
float dy = cy - orig[i + 1];
float dd = dx * dx + dy * dy;
// 计算每个坐标点与当前点(cx、cy)之间的距离
float d = (float) Math.sqrt(dd);
// 计算扭曲度,距离当前点(cx、cy)越远,扭曲度越小
float pull = 100000 / ((float) (dd * d));
// 对verts数组(保存bitmap上21 * 21个点经过扭曲后的坐标)重新赋值
if (pull >= 1)
{
verts[i + 0] = cx;
verts[i + 1] = cy;
}
else
{
// 控制各顶点向触摸事件发生点偏移
verts[i + 0] = orig[i + 0] + dx * pull;
verts[i + 1] = orig[i + 1] + dy * pull;
}
}
// 通知View组件重绘
invalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
// 调用warp方法根据触摸屏事件的坐标点来扭曲verts数组
warp(event.getX(), event.getY());
return true;
}
}
}
public class MainActivity extends Activity
implements OnClickListener
{
// 声明位图渲染对象
private Shader[] shaders = new Shader[5];
// 声明颜色数组
private int[] colors;
MyView myView;
// 自定义视图类
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myView = (MyView)findViewById(R.id.my_view);
// 获得Bitmap实例
Bitmap bm = BitmapFactory.decodeResource(getResources()
, R.drawable.water);
// 设置渐变的颜色组,也就是按红、绿、蓝的方式渐变
colors = new int[] { Color.BLACK, Color.WHITE, Color.GRAY };
// 实例化BitmapShader,x坐标方向重复图形,y坐标方向镜像图形
shaders[0] = new BitmapShader(bm, TileMode.REPEAT,
TileMode.MIRROR);
// 实例化LinearGradient
shaders[1] = new LinearGradient(0, 0, 100, 100
, colors, null, TileMode.REPEAT);
// 实例化RadialGradient
shaders[2] = new RadialGradient(100, 100, 80, colors, null,
TileMode.REPEAT);
// 实例化SweepGradient
shaders[3] = new SweepGradient(160, 160, colors, null);
// 实例化ComposeShader
shaders[4] = new ComposeShader(shaders[1], shaders[2],
PorterDuff.Mode.DARKEN);
Button bn1 = (Button)findViewById(R.id.bn1);
Button bn2 = (Button)findViewById(R.id.bn2);
Button bn3 = (Button)findViewById(R.id.bn3);
Button bn4 = (Button)findViewById(R.id.bn4);
Button bn5 = (Button)findViewById(R.id.bn5);
bn1.setOnClickListener(this);
bn2.setOnClickListener(this);
bn3.setOnClickListener(this);
bn4.setOnClickListener(this);
bn5.setOnClickListener(this);
}
@Override
public void onClick(View source)
{
switch(source.getId())
{
case R.id.bn1:
myView.paint.setShader(shaders[0]);
break;
case R.id.bn2:
myView.paint.setShader(shaders[1]);
break;
case R.id.bn3:
myView.paint.setShader(shaders[2]);
break;
case R.id.bn4:
myView.paint.setShader(shaders[3]);
break;
case R.id.bn5:
myView.paint.setShader(shaders[4]);
break;
}
// 重绘界面
myView.invalidate();
}
}
public class MyView extends View
{
// 声明画笔
public Paint paint;
public MyView(Context context , AttributeSet set)
{
super(context , set);
paint = new Paint();
paint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
// 使用指定Paint对象画矩形
canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
}
}