Android中图像和图像处理
一Bitmap和BitmapFactory
二Android绘图基础
三Path类
PathText
采用双缓冲实现画图板
弹球游戏
使用drawBitmapMesh扭曲图像
使用Shader填充图形
-
-
- 一 Bitmap和BitmapFactory
-
- 代码
-
- Android绘图基础Canvas Paint
-
- 代码
-
- 三Path类
-
- 代码
- PathText
- 代码
-
- 采用双缓冲实现画图板
-
- DrawView代码
- MainActivity代码
- menu_mainxml
-
- 弹球游戏
-
- 代码
-
- 图形特效处理
- Matrix控制变换步骤
- 代码
- Matrix控制变换步骤
- 使用drawBitmapMesh扭曲图像
-
- 代码
-
- 使用Shader填充图形
-
- MainActivity
- MyView代码
-
- 一 Bitmap和BitmapFactory
-
一 Bitmap和BitmapFactory
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)); // ② } }); }}
Android绘图基础,Canvas , Paint
方法 | 简要说明 |
---|---|
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); }}
三Path类
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(); } }}
PathText
代码:
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); } }}
采用双缓冲实现画图板
DrawView代码:
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); }}
MainActivity代码
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_main.xml
<?xml version="1.0" encoding="utf-8"?><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); } } }}
图形特效处理
Matrix控制变换步骤:
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); }}
使用drawBitmapMesh扭曲图像
可以使用此方法在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; } }}
使用Shader填充图形
MainActivity:
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(); }}
MyView代码:
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); }}
更多相关文章
- mybatisplus的坑 insert标签insert into select无参数问题的解决
- Python技巧匿名函数、回调函数和高阶函数
- python list.sort()根据多个关键字排序的方法实现
- ANDROID源代码结构
- textview中加链接
- Android中正确自适应屏幕翻转
- 【Android(安卓)应用开发】 Android(安卓)相关代码规范 更新中 .
- 《第一行代码Android》学习总结第十三章 Android编程技巧
- Android(安卓)如何建立AIDL