Android实现镜像效果(附带源码)

一、项目介绍

1. 背景与应用场景

在图像处理、社交分享、相机预览、游戏特效等场景中,镜像效果(Mirror Effect)极为常见。它可以:

  • 自拍镜像:前置摄像头实时镜像预览,使用户操作更直观

  • 照片编辑:对已有图片做水平或垂直翻转,创造对称美感

  • UI 特效:在游戏或交互动画中快速镜像元素,减少资源占用

  • 文档扫描:对拍摄不正的文档先镜像再校正

本教程将手把手教你在 Android 中实现各种镜像方式,包括:

  1. 简单 ImageView 翻转:利用 ViewscaleX/scaleY

  2. Bitmap 翻转:CPU 端使用 Matrix 操作 Bitmap

  3. Canvas 翻转绘制:在自定义 ViewonDraw 中使用 canvas.scale()

  4. Camera 预览镜像:TextureView + Camera2 API 实时镜像

  5. OpenGL ES 方案:使用 GLSL 着色器做镜像,GPU 加速

  6. RenderScript 方案:通过 RenderScript 对像素做镜像,兼顾 CPU/GPU

最后,我们将上述几种方案封装为一个通用组件 MirrorImageView,以及一个 MirrorCameraView,并提供一键切换水平/垂直/无镜像的 API。


二、相关知识

  1. Android 视图缩放

    • 每个 ViewsetScaleX(float)setScaleY(float),可分别控制水平/垂直缩放(负数即可实现翻转)

  2. Bitmap 与 Matrix

    • Matrix 可对 Bitmap 做平移、缩放(包括负缩放)、旋转、错切;

    • Bitmap.createBitmap(src, 0,0,w,h,matrix,filter) 用于生成新的翻转图像。

  3. Canvas 变换

    • canvas.scale(-1,1, pivotX, pivotY) 先将画布水平翻转,再绘制内容即可实现镜像。

  4. Camera2 + TextureView

    • TextureView 支持对内容做 setTransform(Matrix)

    • 前置摄像头需要将预览流水平镜像给用户“像镜子”一样的体验。

  5. OpenGL ES

    • 在片元着色器中根据纹理坐标 vec2 uv; uv.x = 1.0 - uv.x; 即可水平翻转;

    • 需管理 EGLContextGLSurfaceView

  6. RenderScript

    • 通过编写 .rs 脚本,对每个像素坐标做 dst(x,y)=src(w-1-x,y) 等操作;

    • 与 CPU 操作相比性能更优。


三、实现思路

  1. XML + 属性实现

    • 在布局中通过 app:mirrorMode="none|horizontal|vertical" 控制镜像

    • 自定义 MirrorImageView 继承自 AppCompatImageView,在 onLayoutonDraw 中根据模式设置 scaleX/scaleY

  2. Bitmap 静态翻转

    • 工具方法 fun mirrorBitmap(src: Bitmap, horizontal: Boolean): Bitmap

    • 使用 Matrix.setScale(-1,1)Matrix.setScale(1,-1),并调用 Bitmap.createBitmap

  3. Canvas 翻转画布

    • 自定义 MirrorCanvasView,在 onDraw(Canvas canvas) 前调用 canvas.save(); canvas.scale(...),绘制内容后 canvas.restore()

  4. Camera2 实时镜像

    • 使用 TextureView,获取 SurfaceTexture,启动预览;

    • onSurfaceTextureAvailable 时,根据前/后摄像头选择 textureView.setTransform(mirrorMatrix)

  5. OpenGL ES GPU 加速

    • 使用 GLSurfaceView,在 Renderer.onDrawFrame 调用 GLES20.glUniform1i(uMirror, mode),在 GLSL 中按模式调整 vTexCoord

  6. RenderScript 批量处理

    • ScriptC_mirror.rs 中实现翻转内核,在 Java 中调用 script.forEach_mirror(inAllocation, outAllocation)

  7. 组件化封装

    • MirrorImageView:支持 XML/代码模式,兼容所有 ImageView.setImage* 调用;

    • MirrorCameraView:集成 Camera2 和镜像转换;


四、环境与依赖

// app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'com.android.renderscript'  // 如需 RenderScript

android {
  compileSdkVersion 34
  defaultConfig {
    applicationId "com.example.mirror"
    minSdkVersion 21
    targetSdkVersion 34
    renderscriptTargetApi 21
    renderscriptSupportModeEnabled true
  }
  buildFeatures { viewBinding true }
  kotlinOptions { jvmTarget = "1.8" }
}

dependencies {
  implementation 'androidx.appcompat:appcompat:1.6.1'
  implementation 'androidx.core:core-ktx:1.10.1'
}

五、整合代码

// =======================================================
// 文件: res/values/attrs.xml
// 描述: 定义 MirrorImageView 的 custom 属性
// =======================================================
<resources>
  <declare-styleable name="MirrorImageView">
    <!-- 0=none,1=horizontal,2=vertical -->
    <attr name="mirrorMode" format="integer"/>
  </declare-styleable>
</resources>

// =======================================================
// 文件: MirrorImageView.kt
// 描述: 支持 XML/代码水平或垂直镜像的 ImageView
// =======================================================
package com.example.mirror

import android.content.Context
import android.graphics.Matrix
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView

class MirrorImageView @JvmOverloads constructor(
  context: Context, attrs: AttributeSet? = null
) : AppCompatImageView(context, attrs) {

  companion object {
    const val MODE_NONE = 0
    const val MODE_HORIZONTAL = 1
    const val MODE_VERTICAL = 2
  }

  private var mode = MODE_NONE
  private val matrixMirror = Matrix()

  init {
    context.theme.obtainStyledAttributes(attrs, R.styleable.MirrorImageView,0,0).apply {
      mode = getInt(R.styleable.MirrorImageView_mirrorMode, MODE_NONE)
      recycle()
    }
    scaleType = ScaleType.MATRIX
  }

  override fun onLayout(changed:Boolean, l:Int, t:Int, r:Int, b:Int) {
    super.onLayout(changed,l,t,r,b)
    applyMirror()
  }

  private fun applyMirror() {
    matrixMirror.reset()
    when(mode) {
      MODE_HORIZONTAL -> {
        matrixMirror.setScale(-1f,1f,width/2f, height/2f)
      }
      MODE_VERTICAL -> {
        matrixMirror.setScale(1f,-1f,width/2f, height/2f)
      }
      else -> { /* no-op */ }
    }
    imageMatrix = matrixMirror
  }

  /** 动态设置镜像模式 */
  fun setMirrorMode(m: Int) {
    mode = m
    applyMirror()
    invalidate()
  }
}

// =======================================================
// 文件: ImageUtils.kt
// 描述: Bitmap 镜像工具:水平或垂直翻转
// =======================================================
package com.example.mirror

import android.graphics.Bitmap
import android.graphics.Matrix

object ImageUtils {
  /**
   * 镜像位图
   * @param src 原始 Bitmap
   * @param horizontal true 水平翻转,false 垂直翻转
   */
  fun mirrorBitmap(src: Bitmap, horizontal: Boolean): Bitmap {
    val matrix = Matrix()
    if (horizontal) matrix.setScale(-1f, 1f)
    else            matrix.setScale(1f, -1f)
    return Bitmap.createBitmap(src,0,0,src.width,src.height,matrix,true)
  }
}

// =======================================================
// 文件: MirrorCanvasView.kt
// 描述: Canvas 翻转绘制示例,子类需重写 drawContent()
// =======================================================
package com.example.mirror

import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.View

abstract class MirrorCanvasView @JvmOverloads constructor(
  ctx: Context, attrs: AttributeSet? = null
): View(ctx, attrs) {

  enum class Mode { NONE, HORIZONTAL, VERTICAL }
  var mode = Mode.NONE

  override fun onDraw(canvas: Canvas) {
    canvas.save()
    when(mode) {
      Mode.HORIZONTAL -> canvas.scale(-1f,1f,width/2f, height/2f)
      Mode.VERTICAL   -> canvas.scale(1f,-1f,width/2f, height/2f)
      else -> {}
    }
    drawContent(canvas)
    canvas.restore()
  }

  /** 在此方法中绘制实际内容 */
  abstract fun drawContent(canvas: Canvas)
}

// =======================================================
// 文件: res/layout/activity_main.xml
// 描述: 示例布局:MirrorImageView、按钮切换模式及 Bitmap 翻转
// =======================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:padding="16dp"
    android:layout_width="match_parent" android:layout_height="match_parent">

  <com.example.mirror.MirrorImageView
      android:id="@+id/imgMirror"
      android:layout_width="200dp"
      android:layout_height="200dp"
      android:src="@drawable/sample_pic"
      app:mirrorMode="0"/>

  <Button android:id="@+id/btnNone"       android:layout_marginTop="8dp"
      android:layout_width="wrap_content" android:layout_height="wrap_content"
      android:text="无镜像"/>
  <Button android:id="@+id/btnHorz"       android:layout_marginTop="4dp"
      android:layout_width="wrap_content" android:layout_height="wrap_content"
      android:text="水平镜像"/>
  <Button android:id="@+id/btnVert"       android:layout_marginTop="4dp"
      android:layout_width="wrap_content" android:layout_height="wrap_content"
      android:text="垂直镜像"/>

  <ImageView android:id="@+id/imgBitmap"  android:layout_marginTop="16dp"
      android:layout_width="200dp" android:layout_height="200dp"
      android:src="@drawable/sample_pic"/>
  <Button android:id="@+id/btnFlipBmp"    android:layout_marginTop="4dp"
      android:layout_width="wrap_content" android:layout_height="wrap_content"
      android:text="Bitmap 水平翻转"/>

</LinearLayout>

// =======================================================
// 文件: MainActivity.kt
// 描述: 绑定 UI 控件,演示 MirrorImageView 与 Bitmap 翻转
// =======================================================
package com.example.mirror

import android.graphics.BitmapFactory
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.mirror.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
  private lateinit var binding: ActivityMainBinding

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    binding.btnNone.setOnClickListener {
      binding.imgMirror.setMirrorMode(MirrorImageView.MODE_NONE)
    }
    binding.btnHorz.setOnClickListener {
      binding.imgMirror.setMirrorMode(MirrorImageView.MODE_HORIZONTAL)
    }
    binding.btnVert.setOnClickListener {
      binding.imgMirror.setMirrorMode(MirrorImageView.MODE_VERTICAL)
    }

    // Bitmap 翻转示例
    val bmp = BitmapFactory.decodeResource(resources, R.drawable.sample_pic)
    binding.btnFlipBmp.setOnClickListener {
      val mirrorBmp = ImageUtils.mirrorBitmap(bmp, true)
      binding.imgBitmap.setImageBitmap(mirrorBmp)
    }
  }
}

// =======================================================
// 文件: MirrorCameraView.kt
// 描述: Camera2 + TextureView 实时预览镜像示例
// =======================================================
package com.example.mirror

import android.content.Context
import android.graphics.Matrix
import android.util.AttributeSet
import android.view.TextureView
import android.view.Surface
import android.view.View

class MirrorCameraView @JvmOverloads constructor(
  ctx: Context, attrs: AttributeSet? = null
): TextureView(ctx, attrs) {

  enum class Mode { NONE, HORIZONTAL, VERTICAL }
  var mode = Mode.NONE

  fun updateTransform() {
    if (!isAvailable) {
      surfaceTextureListener = object: SurfaceTextureListener {
        override fun onSurfaceTextureAvailable(st, w, h) { applyTransform(w,h) }
        override fun onSurfaceTextureSizeChanged(st, w, h) { applyTransform(w,h) }
        override fun onSurfaceTextureDestroyed(st) = true
        override fun onSurfaceTextureUpdated(st) {}
      }
    } else applyTransform(width, height)
  }

  private fun applyTransform(viewW:Int, viewH:Int) {
    val matrix = Matrix()
    when(mode) {
      Mode.HORIZONTAL -> matrix.setScale(-1f,1f,viewW/2f, viewH/2f)
      Mode.VERTICAL   -> matrix.setScale(1f,-1f,viewW/2f, viewH/2f)
      else -> {}
    }
    transform = matrix
  }
}

// =======================================================
// 文件: OpenGLMirrorRenderer.kt
// 描述: 基于 GLSurfaceView/OpenGL ES 的镜像 Renderer(伪代码示例)
// =======================================================
package com.example.mirror

import android.opengl.GLES20
import android.opengl.GLSurfaceView
import javax.microedition.khronos.opengles.GL10

class OpenGLMirrorRenderer: GLSurfaceView.Renderer {
  var mirrorHorizontal = false
  override fun onSurfaceCreated(gl: GL10, config: javax.microedition.khronos.egl.EGLConfig) { /* init shaders */ }
  override fun onSurfaceChanged(gl: GL10, w: Int, h: Int) { GLES20.glViewport(0,0,w,h) }
  override fun onDrawFrame(gl: GL10) {
    // 设置 uniform uMirror
    GLES20.glUniform1i(uMirrorLocation, if(mirrorHorizontal)1 else 0)
    // draw texture quad
  }
}

// =======================================================
// 文件: ScriptC_mirror.rs
// 描述: RenderScript 镜像脚本(水平镜像示例)
// =======================================================
#pragma version(1)
#pragma rs java_package_name(com.example.mirror)

rs_allocation inAlloc;
int width;

uchar4 __attribute__((kernel)) mirror(uint32_t x, uint32_t y) {
  return rsGetElementAt_uchar4(inAlloc, width-1-x, y);
}

六、代码解读

  1. MirrorImageView

    • 继承 AppCompatImageView,使用自定义属性 mirrorMode 决定 Matrix 操作;

    • onLayout 后调用 applyMirror(),通过 scale(-1,1)scale(1,-1) 实现镜像。

  2. ImageUtils.mirrorBitmap

    • CPU 端静态翻转 Bitmap,生成新对象,适合对已加载图片批量处理。

  3. MirrorCanvasView

    • 抽象出 canvas.save()canvas.scale(...)drawContent()canvas.restore() 模板,方便子类绘制时复用。

  4. MirrorCameraView

    • 基于 TextureView 的预览镜像,通过 setTransform(Matrix) 给 Camera2 预览流实时应用镜像。

  5. OpenGL ES

    • 在 Fragment 着色器中通过传入 uniform 决定是否将纹理坐标 u = 1.0 - u,在 GPU 端高效实现镜像。

  6. RenderScript

    • .rs 脚本对每个像素位置 (x,y) 读取输入 inAlloc(w-1-x,y),在后台高效并行运行。


七、性能与优化

  1. GPU vs CPU

    • 单张 Bitmap 静态镜像:CPU via Matrix 足矣;

    • 快速连续预览(Camera、游戏):优先使用 GPU(OpenGL ES 或 TextureView transform);

  2. Reuse vs Allocation

    • 避免在高频交互(滑动、手势)中频繁创建新 Bitmap,可复用同大小 BitmapCanvas

  3. 硬件加速

    • 确保 MirrorCanvasViewMirrorImageView 的硬件加速开启(默认开启);

  4. 线程

    • Bitmap 翻转等 I/O 或重计算任务放在后台线程执行,防止 UI 卡顿;


八、项目总结与拓展

  • 本文系统介绍了 Android 中多种镜像实现方案,从最简的 View.scaleX=-1 到 GPU 着色器、RenderScript。

  • 为业务层封装了 MirrorImageViewImageUtils 等通用组件,大幅降低集成成本。

拓展方向

  1. 动态切换动画:在镜像切换时添加淡入/滑动动画,提升交互体验;

  2. 3D 旋转:结合 Camera3D 投影矩阵,实现沿垂直或水平轴的 3D 翻转;

  3. 视频镜像:对 MediaPlayerExoPlayer 的视频输出进行镜像处理;

  4. 支持更多效果:如旋转倒影、渐变反射、TikTok 式对称分屏。


九、FAQ

Q1:为什么 ImageView scaleX=-1 会镜像但内容偏移?
A1:scale(-1,1) 默认以 View 左上角为原点翻转,需设置 pivotX=width/2 来居中。

Q2:Bitmap 翻转后质量会变差吗?
A2:Matrix 操作支持双线性滤波(filter=true);若模糊可手动禁用滤波或使用高质量渲染。

Q3:Camera2 预览镜像对性能影响大吗?
A3:TextureView.transform 在 GPU 端执行,不占用 CPU,性能基本无异。

Q4:RenderScript 何时优于 CPU?
A4:当处理大分辨率图片或需要批量像素操作时,RenderScript 自动并行化,性能更优。

Q5:OpenGL ES 是否兼容所有设备?
A5:Android 2.2+ 大部分设备支持 OpenGL ES 2.0,需做版本兼容检查。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值