Android studio之图形与图像处理

    xiaoxiao2025-07-26  11

    前言: 众所周知,决定一个Android应用是否可以被用户接受最重要的方面就是用户界面,为了让我们的Android给用户提供一个更友好的界面,就需要我们在应用中使用和插入图片了。Android系统提供了丰富的图片功能支持,其中就包括处理静态图片和动画等等。 一、使用简单的图片 (1)使用Drawable对象 为Android应用添加了Drawable资源之后,Android SDK会为这份资源在R清单文件中创建一个索引项:R.drawable.file_name。 接下来即可在XML资源文件中通过@drawable/file_name访问该Drawable对象,也可以在Java代码中通过R.drawable.file_name访问改对象。 (2)Bitmap和BitmapFactory Bitmap代表一个位图,而BitmapDrawable中封装的图片就是一个Bitmap对象,开发者为了把一个Bitmap对象包装成BitmapDrawable对象,可以调用BitmapDrawable的构造器:

    BitmapDrawable drawable = new BitmapDrawable(bitmap);

    果需要获取BitmapDrawable所包装的Bitmap对象,则可调用 BitmapDrawable中的getBitmap()方法:

    Bitmap bitmap = drawable.getBitmap();

    (3)下面我们用以上知识实现一个图片查看器 我们这个应用十分简单,仅仅包括一个按钮和一个ImageView,当我们点击按钮时候,程序会自动去搜索目录中的下一张图片:     关键代码如下:

    import android.app.Activity; import android.content.res.AssetManager; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageView; import java.io.IOException; import java.io.InputStream; 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(); } 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)); } }); } }

    结果Gif

    二、绘图 在为我们的应用界面添砖加瓦的过程中,除了可以使用已有的图片外,Android应用还经常需要在运行过程中动态的生成图片,而这样就需要借助于Android的绘图支持了。     (1)绘图基础——Canvas、Paint

    Android的绘图与Swing中的绘图思路类似即开发一个自定义类,然后让该类继承JPanel,之后重写其中的paint(Graphics g)方法即可。而我们Android中的绘图应继承View组件,并重写其中的onDraw(Canvas canvas)即可,光说不练假把式,接下来我们用一段代码绘制几个集合图形。

    关键代码如下:

    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.util.AttributeSet; import android.view.View; 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.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); // 为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.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); } }

    结果截图如下:

    (2)Path类 Android提供的Path是一个非常有用的类,它可以在View上将几个点连成一条路径,然后可以调用Canvas中的drawPath(path,paint)方法沿着该路径进行绘图,这些所谓的绘图效果说起来大家很难理解,下面我们通过一段代码来让大家更好的理解一下这些效果,这个应用绘制了7条路径:

    关键代码如下:

    import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ComposePathEffect; import android.graphics.CornerPathEffect; import android.graphics.DashPathEffect; import android.graphics.DiscretePathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathDashPathEffect; import android.graphics.PathEffect; import android.graphics.SumPathEffect; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; 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++) { 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); // 不使用路径效果 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(); } } }

    结果Gif

    三、实例训练 让我们根据今天所学做两个小小的实例练习吧。 (1)手绘画板

    我们主要要实现一个画板,当我们在触摸屏上移动时,就可以在屏幕上绘制任意的图案。 表面上看起来我们可以在画板上自由地画画包括直、曲线,实际上我们还是利用Canvas的drawLine()方法画直线线。当我们在画板上移动时,两次拖动时间发生点的距离很小,我们用多条极短的直线将他们连起来就好像我们画的是曲线。主要我们还是借助Android提供的Path类。

    关键代码如下:

    View代码:

    import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; 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); } }

    颜色、大小代码:

    <?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>

    结果Gif

    (2)弹球小游戏

    该应用实现了一个简单的弹球游戏,其中小球和球拍分别以圆形和矩形代替,小球开始以随机速度向下运动,遇到边框或球拍时小球反弹。球拍则由用户控制,当用户按下向A、向D键时,球拍将会向左、向右移动。

    关键代码如下:

    import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.DisplayMetrics; import android.view.Display; import android.view.KeyEvent; import android.view.View; import android.view.View.OnKeyListener; import android.view.Window; import android.view.WindowManager; import java.util.Random; import java.util.Timer; import java.util.TimerTask; 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(); 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); } } } }

    结果Gif

    作者:空蝉缝花Q 原文地址:https://blog.csdn.net/weixin_43901698/article/details/90581993

    最新回复(0)