一、项目介绍
1. 背景与应用场景
在图像处理、社交分享、相机预览、游戏特效等场景中,镜像效果(Mirror Effect)极为常见。它可以:
-
自拍镜像:前置摄像头实时镜像预览,使用户操作更直观
-
照片编辑:对已有图片做水平或垂直翻转,创造对称美感
-
UI 特效:在游戏或交互动画中快速镜像元素,减少资源占用
-
文档扫描:对拍摄不正的文档先镜像再校正
本教程将手把手教你在 Android 中实现各种镜像方式,包括:
-
简单 ImageView 翻转:利用
View
的scaleX/scaleY
-
Bitmap 翻转:CPU 端使用
Matrix
操作Bitmap
-
Canvas 翻转绘制:在自定义
View
的onDraw
中使用canvas.scale()
-
Camera 预览镜像:TextureView + Camera2 API 实时镜像
-
OpenGL ES 方案:使用 GLSL 着色器做镜像,GPU 加速
-
RenderScript 方案:通过 RenderScript 对像素做镜像,兼顾 CPU/GPU
最后,我们将上述几种方案封装为一个通用组件 MirrorImageView
,以及一个 MirrorCameraView
,并提供一键切换水平/垂直/无镜像的 API。
二、相关知识
-
Android 视图缩放
-
每个
View
有setScaleX(float)
和setScaleY(float)
,可分别控制水平/垂直缩放(负数即可实现翻转)
-
-
Bitmap 与 Matrix
-
Matrix
可对Bitmap
做平移、缩放(包括负缩放)、旋转、错切; -
Bitmap.createBitmap(src, 0,0,w,h,matrix,filter)
用于生成新的翻转图像。
-
-
Canvas 变换
-
canvas.scale(-1,1, pivotX, pivotY)
先将画布水平翻转,再绘制内容即可实现镜像。
-
-
Camera2 + TextureView
-
TextureView
支持对内容做setTransform(Matrix)
; -
前置摄像头需要将预览流水平镜像给用户“像镜子”一样的体验。
-
-
OpenGL ES
-
在片元着色器中根据纹理坐标
vec2 uv; uv.x = 1.0 - uv.x;
即可水平翻转; -
需管理
EGLContext
、GLSurfaceView
。
-
-
RenderScript
-
通过编写
.rs
脚本,对每个像素坐标做dst(x,y)=src(w-1-x,y)
等操作; -
与 CPU 操作相比性能更优。
-
三、实现思路
-
XML + 属性实现
-
在布局中通过
app:mirrorMode="none|horizontal|vertical"
控制镜像 -
自定义
MirrorImageView
继承自AppCompatImageView
,在onLayout
或onDraw
中根据模式设置scaleX
/scaleY
。
-
-
Bitmap 静态翻转
-
工具方法
fun mirrorBitmap(src: Bitmap, horizontal: Boolean): Bitmap
-
使用
Matrix.setScale(-1,1)
或Matrix.setScale(1,-1)
,并调用Bitmap.createBitmap
。
-
-
Canvas 翻转画布
-
自定义
MirrorCanvasView
,在onDraw(Canvas canvas)
前调用canvas.save(); canvas.scale(...)
,绘制内容后canvas.restore()
。
-
-
Camera2 实时镜像
-
使用
TextureView
,获取SurfaceTexture
,启动预览; -
在
onSurfaceTextureAvailable
时,根据前/后摄像头选择textureView.setTransform(mirrorMatrix)
。
-
-
OpenGL ES GPU 加速
-
使用
GLSurfaceView
,在Renderer.onDrawFrame
调用GLES20.glUniform1i(uMirror, mode)
,在 GLSL 中按模式调整vTexCoord
。
-
-
RenderScript 批量处理
-
在
ScriptC_mirror.rs
中实现翻转内核,在 Java 中调用script.forEach_mirror(inAllocation, outAllocation)
。
-
-
组件化封装
-
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);
}
六、代码解读
-
MirrorImageView
-
继承
AppCompatImageView
,使用自定义属性mirrorMode
决定Matrix
操作; -
在
onLayout
后调用applyMirror()
,通过scale(-1,1)
或scale(1,-1)
实现镜像。
-
-
ImageUtils.mirrorBitmap
-
CPU 端静态翻转
Bitmap
,生成新对象,适合对已加载图片批量处理。
-
-
MirrorCanvasView
-
抽象出
canvas.save()
→canvas.scale(...)
→drawContent()
→canvas.restore()
模板,方便子类绘制时复用。
-
-
MirrorCameraView
-
基于
TextureView
的预览镜像,通过setTransform(Matrix)
给 Camera2 预览流实时应用镜像。
-
-
OpenGL ES
-
在 Fragment 着色器中通过传入 uniform 决定是否将纹理坐标
u = 1.0 - u
,在 GPU 端高效实现镜像。
-
-
RenderScript
-
.rs
脚本对每个像素位置(x,y)
读取输入inAlloc
的(w-1-x,y)
,在后台高效并行运行。
-
七、性能与优化
-
GPU vs CPU
-
单张
Bitmap
静态镜像:CPU viaMatrix
足矣; -
快速连续预览(Camera、游戏):优先使用 GPU(OpenGL ES 或 TextureView transform);
-
-
Reuse vs Allocation
-
避免在高频交互(滑动、手势)中频繁创建新
Bitmap
,可复用同大小Bitmap
和Canvas
;
-
-
硬件加速
-
确保
MirrorCanvasView
或MirrorImageView
的硬件加速开启(默认开启);
-
-
线程
-
Bitmap 翻转等 I/O 或重计算任务放在后台线程执行,防止 UI 卡顿;
-
八、项目总结与拓展
-
本文系统介绍了 Android 中多种镜像实现方案,从最简的
View.scaleX=-1
到 GPU 着色器、RenderScript。 -
为业务层封装了
MirrorImageView
、ImageUtils
等通用组件,大幅降低集成成本。
拓展方向
-
动态切换动画:在镜像切换时添加淡入/滑动动画,提升交互体验;
-
3D 旋转:结合 Camera3D 投影矩阵,实现沿垂直或水平轴的 3D 翻转;
-
视频镜像:对
MediaPlayer
或ExoPlayer
的视频输出进行镜像处理; -
支持更多效果:如旋转倒影、渐变反射、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,需做版本兼容检查。