commit 44a7080aa32bdf2bd1cdab2e92a06b98a8ed0ee6 Author: lijm Date: Thu Jun 19 11:19:32 2025 +0800 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e19105 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +* android_magnifer: 安卓实现放大镜的参考代码 \ No newline at end of file diff --git a/android_magnifer/.gitignore b/android_magnifer/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/android_magnifer/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/android_magnifer/.idea/.gitignore b/android_magnifer/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/android_magnifer/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/android_magnifer/.idea/.name b/android_magnifer/.idea/.name new file mode 100644 index 0000000..b3405b3 --- /dev/null +++ b/android_magnifer/.idea/.name @@ -0,0 +1 @@ +My Application \ No newline at end of file diff --git a/android_magnifer/.idea/compiler.xml b/android_magnifer/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/android_magnifer/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android_magnifer/.idea/deploymentTargetSelector.xml b/android_magnifer/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/android_magnifer/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/android_magnifer/.idea/git_toolbox_blame.xml b/android_magnifer/.idea/git_toolbox_blame.xml new file mode 100644 index 0000000..7dc1249 --- /dev/null +++ b/android_magnifer/.idea/git_toolbox_blame.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android_magnifer/.idea/gradle.xml b/android_magnifer/.idea/gradle.xml new file mode 100644 index 0000000..ae733f1 --- /dev/null +++ b/android_magnifer/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/android_magnifer/.idea/kotlinc.xml b/android_magnifer/.idea/kotlinc.xml new file mode 100644 index 0000000..148fdd2 --- /dev/null +++ b/android_magnifer/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android_magnifer/.idea/migrations.xml b/android_magnifer/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/android_magnifer/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/android_magnifer/.idea/misc.xml b/android_magnifer/.idea/misc.xml new file mode 100644 index 0000000..74dd639 --- /dev/null +++ b/android_magnifer/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/android_magnifer/.idea/runConfigurations.xml b/android_magnifer/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/android_magnifer/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/android_magnifer/app/.gitignore b/android_magnifer/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/android_magnifer/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/android_magnifer/app/build.gradle.kts b/android_magnifer/app/build.gradle.kts new file mode 100644 index 0000000..6884058 --- /dev/null +++ b/android_magnifer/app/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.feima.myapplication" + compileSdk = 35 + + defaultConfig { + applicationId = "com.feima.myapplication" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} \ No newline at end of file diff --git a/android_magnifer/app/proguard-rules.pro b/android_magnifer/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/android_magnifer/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/android_magnifer/app/src/androidTest/java/com/feima/myapplication/ExampleInstrumentedTest.kt b/android_magnifer/app/src/androidTest/java/com/feima/myapplication/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..1f3db99 --- /dev/null +++ b/android_magnifer/app/src/androidTest/java/com/feima/myapplication/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.feima.myapplication + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.feima.myapplication", appContext.packageName) + } +} \ No newline at end of file diff --git a/android_magnifer/app/src/main/AndroidManifest.xml b/android_magnifer/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..50c86d4 --- /dev/null +++ b/android_magnifer/app/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android_magnifer/app/src/main/java/com/feima/myapplication/ImageDisplayActivity.kt b/android_magnifer/app/src/main/java/com/feima/myapplication/ImageDisplayActivity.kt new file mode 100644 index 0000000..4418416 --- /dev/null +++ b/android_magnifer/app/src/main/java/com/feima/myapplication/ImageDisplayActivity.kt @@ -0,0 +1,16 @@ +package com.feima.myapplication + +import android.os.Bundle +import android.widget.ImageView +import androidx.appcompat.app.AppCompatActivity + +class ImageDisplayActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_image_display) + + val imageView = findViewById(R.id.image_view) + // 设置显示应用图标作为示例图片 + imageView.setImageResource(R.mipmap.ic_launcher) + } +} \ No newline at end of file diff --git a/android_magnifer/app/src/main/java/com/feima/myapplication/MagnifierActivity.kt b/android_magnifer/app/src/main/java/com/feima/myapplication/MagnifierActivity.kt new file mode 100644 index 0000000..6776930 --- /dev/null +++ b/android_magnifer/app/src/main/java/com/feima/myapplication/MagnifierActivity.kt @@ -0,0 +1,275 @@ +package com.feima.myapplication + +import android.content.Context +import android.graphics.* +import android.os.Bundle +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.SurfaceHolder +import android.view.SurfaceView +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat + +class MagnifierActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_magnifier) + } +} + +class MagnifierSurfaceView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : SurfaceView(context, attrs, defStyleAttr), SurfaceHolder.Callback { + + private var surfaceHolder: SurfaceHolder = holder + private var bitmap: Bitmap? = null + private var isLongPressed = false + private var touchX = 0f + private var touchY = 0f + private val magnifierRadius = 150f + private val magnificationFactor = 2.5f + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val magnifierPaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG) + + init { + surfaceHolder.addCallback(this) + setWillNotDraw(false) + + // 设置放大镜边框样式 + borderPaint.apply { + color = Color.WHITE + style = Paint.Style.STROKE + strokeWidth = 8f + } + + // 加载示例图片 + loadSampleImage() + } + + private fun loadSampleImage() { + try { + // 从drawable资源加载矢量图片 + val drawable = ContextCompat.getDrawable(context, R.drawable.sample_image) + drawable?.let { + val width = 800 + val height = 600 + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap!!) + + // 设置drawable的边界并绘制到bitmap + it.setBounds(0, 0, width, height) + it.draw(canvas) + + // 添加一些额外的细节以便测试放大镜效果 + val detailPaint = Paint(Paint.ANTI_ALIAS_FLAG) + detailPaint.color = Color.WHITE + detailPaint.strokeWidth = 1f + + // 绘制细网格 + for (i in 0 until width step 25) { + canvas.drawLine(i.toFloat(), 0f, i.toFloat(), height.toFloat(), detailPaint) + } + for (i in 0 until height step 25) { + canvas.drawLine(0f, i.toFloat(), width.toFloat(), i.toFloat(), detailPaint) + } + + // 添加一些小的细节点 + detailPaint.color = Color.BLACK + for (i in 0 until 30) { + val x = (Math.random() * width).toFloat() + val y = (Math.random() * height).toFloat() + canvas.drawCircle(x, y, 3f, detailPaint) + } + } + } catch (e: Exception) { + // 如果加载失败,创建一个简单的示例图片 + createFallbackImage() + } + } + + private fun createFallbackImage() { + val width = 800 + val height = 600 + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap!!) + + // 绘制渐变背景 + val gradient = LinearGradient( + 0f, 0f, width.toFloat(), height.toFloat(), + intArrayOf(Color.BLUE, Color.GREEN, Color.YELLOW, Color.RED), + null, Shader.TileMode.CLAMP + ) + val gradientPaint = Paint() + gradientPaint.shader = gradient + canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gradientPaint) + + // 绘制一些细节图案 + val detailPaint = Paint(Paint.ANTI_ALIAS_FLAG) + detailPaint.color = Color.WHITE + detailPaint.strokeWidth = 2f + + // 绘制网格 + for (i in 0 until width step 50) { + canvas.drawLine(i.toFloat(), 0f, i.toFloat(), height.toFloat(), detailPaint) + } + for (i in 0 until height step 50) { + canvas.drawLine(0f, i.toFloat(), width.toFloat(), i.toFloat(), detailPaint) + } + } + + override fun surfaceCreated(holder: SurfaceHolder) { + drawImage() + } + + override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + drawImage() + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + // 清理资源 + } + + private fun drawImage() { + if (!surfaceHolder.surface.isValid) return + + val canvas = surfaceHolder.lockCanvas() ?: return + + try { + // 清空画布 + canvas.drawColor(Color.BLACK) + + bitmap?.let { bmp -> + // 计算图片缩放以适应屏幕 + val scaleX = width.toFloat() / bmp.width + val scaleY = height.toFloat() / bmp.height + val scale = minOf(scaleX, scaleY) + + val scaledWidth = bmp.width * scale + val scaledHeight = bmp.height * scale + val left = (width - scaledWidth) / 2 + val top = (height - scaledHeight) / 2 + + val destRect = RectF(left, top, left + scaledWidth, top + scaledHeight) + canvas.drawBitmap(bmp, null, destRect, paint) + + // 如果长按,绘制放大镜 + if (isLongPressed) { + drawMagnifier(canvas, bmp, destRect) + } + } + } finally { + surfaceHolder.unlockCanvasAndPost(canvas) + } + } + + private fun drawMagnifier(canvas: Canvas, bitmap: Bitmap, imageRect: RectF) { + // 计算触摸点在原图中的位置 + val relativeX = (touchX - imageRect.left) / imageRect.width() + val relativeY = (touchY - imageRect.top) / imageRect.height() + + if (relativeX < 0 || relativeX > 1 || relativeY < 0 || relativeY > 1) return + + val sourceX = (relativeX * bitmap.width).toInt() + val sourceY = (relativeY * bitmap.height).toInt() + + // 计算源区域 + val sourceRadius = (magnifierRadius / magnificationFactor).toInt() + val sourceLeft = maxOf(0, sourceX - sourceRadius) + val sourceTop = maxOf(0, sourceY - sourceRadius) + val sourceRight = minOf(bitmap.width, sourceX + sourceRadius) + val sourceBottom = minOf(bitmap.height, sourceY + sourceRadius) + + val sourceRect = Rect(sourceLeft, sourceTop, sourceRight, sourceBottom) + + // 计算放大镜显示位置(避免被手指遮挡) + var magnifierX = touchX + var magnifierY = touchY - magnifierRadius - 100f + + // 确保放大镜在屏幕内 + if (magnifierY - magnifierRadius < 0) { + magnifierY = touchY + magnifierRadius + 100f + } + if (magnifierX - magnifierRadius < 0) { + magnifierX = magnifierRadius + } + if (magnifierX + magnifierRadius > width) { + magnifierX = width - magnifierRadius + } + + // 创建圆形裁剪路径 + val clipPath = Path() + clipPath.addCircle(magnifierX, magnifierY, magnifierRadius, Path.Direction.CW) + + // 保存画布状态 + canvas.save() + canvas.clipPath(clipPath) + + // 绘制放大的图像 + val destRect = RectF( + magnifierX - magnifierRadius, + magnifierY - magnifierRadius, + magnifierX + magnifierRadius, + magnifierY + magnifierRadius + ) + + canvas.drawBitmap(bitmap, sourceRect, destRect, magnifierPaint) + + // 恢复画布状态 + canvas.restore() + + // 绘制放大镜边框 + canvas.drawCircle(magnifierX, magnifierY, magnifierRadius, borderPaint) + + // 绘制十字线 + val crossPaint = Paint(Paint.ANTI_ALIAS_FLAG) + crossPaint.color = Color.RED + crossPaint.strokeWidth = 3f + val crossSize = 20f + + canvas.drawLine( + magnifierX - crossSize, magnifierY, + magnifierX + crossSize, magnifierY, + crossPaint + ) + canvas.drawLine( + magnifierX, magnifierY - crossSize, + magnifierX, magnifierY + crossSize, + crossPaint + ) + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + touchX = event.x + touchY = event.y + // 开始长按检测 + postDelayed({ + isLongPressed = true + drawImage() + }, 500) // 500ms后激活放大镜 + return true + } + MotionEvent.ACTION_MOVE -> { + if (isLongPressed) { + touchX = event.x + touchY = event.y + drawImage() + } + return true + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + removeCallbacks(null) // 取消长按检测 + if (isLongPressed) { + isLongPressed = false + drawImage() + } + return true + } + } + return super.onTouchEvent(event) + } +} \ No newline at end of file diff --git a/android_magnifer/app/src/main/java/com/feima/myapplication/MainActivity.kt b/android_magnifer/app/src/main/java/com/feima/myapplication/MainActivity.kt new file mode 100644 index 0000000..f137bba --- /dev/null +++ b/android_magnifer/app/src/main/java/com/feima/myapplication/MainActivity.kt @@ -0,0 +1,19 @@ +package com.feima.myapplication + +import android.content.Intent +import android.os.Bundle +import android.widget.Button +import androidx.appcompat.app.AppCompatActivity + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + val btnShowImage = findViewById