View->Bitmap缩放到自定义ViewGroup的任意区域

Bitmap缩放和平移

  • 加载一张Bitmap可能为宽高相同的正方形,也可能为宽高不同的矩形
  • 缩放方向可以为中心缩放,左上角缩放,右上角缩放,左下角缩放,右下角缩放
  • Bitmap中心缩放,包含了缩放和平移两个操作,不可拆开
  • Bitmap其余四个方向的缩放,可以单独缩放不带平移,也可以缩放带平移

XML文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.yang.app.MyRelativeLayout
        android:id="@+id/real_rl"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginTop="30dp"
        android:layout_marginBottom="30dp"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:background="@color/gray">
        <com.yang.app.MyImageView
            android:id="@+id/real_iv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="30dp"
            android:layout_marginBottom="30dp"
            android:layout_marginLeft="30dp"
            android:layout_marginRight="30dp" />
    </com.yang.app.MyRelativeLayout>

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_weight="0"
        android:background="#00ff00" />
</LinearLayout>

Activity代码

const val TAG = "Yang"
class MainActivity : AppCompatActivity() {
    var mRealView : MyImageView ?= null
    var mRelativeLayout : MyRelativeLayout ?= null
    var tempBitmap : Bitmap ?= null
    var mHandler = Handler(Looper.getMainLooper())
    var screenWidth = 0
    var screenHeight = 0
    var srcRect = RectF()
    var destRect = RectF()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mRealView = findViewById(R.id.real_iv)
        mRelativeLayout = findViewById(R.id.real_rl)
        // 屏幕宽高的一半作为临时RectF, 用于压缩Bitmap
        screenWidth = resources.displayMetrics.widthPixels
        screenHeight = resources.displayMetrics.heightPixels
        val tempRect = RectF(0f, 0f, screenWidth.toFloat() / 2, screenHeight.toFloat() / 2)
        
        
        CoroutineScope(Dispatchers.IO).launch {
            tempBitmap = getBitmap(resources, tempRect, R.drawable.fake)
            withContext(Dispatchers.Main) {
                mRelativeLayout?.post {
                    // 获取初始区域的RectF
                    srcRect.set(
                        0f,
                        0f,
                        mRelativeLayout?.width?.toFloat()!!,
                        mRelativeLayout?.height?.toFloat()!!
                    )
                    // 获取结束区域的RectF
                    mRelativeLayout?.forEach { childView ->
                        if (childView is MyImageView) {
                            destRect.set(
                                0f,
                                0f,
                                 childView.width.toFloat(),
                                childView.height.toFloat()
                            )
                        }
                    }
                    scaleRectFun(tempBitmap, srcRect, destRect)
                }
            }
        }
    }
    
    fun scaleRectFun(tempBitmap: Bitmap?, srcRect : RectF, destRect: RectF){
        tempBitmap?.let { bitmap->
            mRelativeLayout?.setBitmap(bitmap)
            val animator = ValueAnimator.ofFloat(0f, 1f)
            animator.duration = 5000L
            animator.interpolator = DecelerateInterpolator()
            animator.addUpdateListener {
                val value = it.animatedValue as Float
                // 中心缩放
                mRelativeLayout?.setDestRectCenterWithTranslate(srcRect, destRect, value)
                // 左上角不带平移缩放
                // mRelativeLayout?.setDestRectLeftTopNoTranslate(srcRect, destRect, value)
                // 左上角平移缩放
                // mRelativeLayout?.setDestRectLeftTopWithTranslate(srcRect, destRect, value)
                // 右上角不带平移缩放
                // mRelativeLayout?.setDestRectRightTopNoTranslate(srcRect, destRect, value)
                // 右上角平移缩放
                // mRelativeLayout?.setDestRectRightTopWithTranslate(srcRect, destRect, value)
                // 左下角不带平移缩放
                // mRelativeLayout?.setDestRectLeftBottomNoTranslate(srcRect, destRect, value)
                // 左下角平移缩放
                // mRelativeLayout?.setDestRectLeftBottomWithTranslate(srcRect, destRect, value)
                // 右下角不带平移缩放
                // mRelativeLayout?.setDestRectRightBottomNoTranslate(srcRect, destRect, value)
                // 右下角平移缩放
                // mRelativeLayout?.setDestRectRightBottomWithTranslate(srcRect, destRect, value)
            }
            animator.start()
        }
    }
}
fun getBitmap(resources : Resources, destRect : RectF, imageId: Int): Bitmap? {
    var imageWidth = -1
    var imageHeight = -1
    val preOption = BitmapFactory.Options().apply {
        // 只获取图片的宽高
        inJustDecodeBounds = true
        BitmapFactory.decodeResource(resources, imageId, this)
    }
    imageWidth = preOption.outWidth
    imageHeight = preOption.outHeight
    // 计算缩放比例
    val scaleMatrix = Matrix()
    // 确定未缩放Bitmap的RectF
    var srcRect = RectF(0f, 0f, imageWidth.toFloat(), imageHeight.toFloat())
    // 通过目标RectF, 确定缩放数值,存储在scaleMatrix中
    scaleMatrix.setRectToRect(srcRect, destRect, Matrix.ScaleToFit.CENTER)
    // 缩放数值再映射到原始Bitmap上,得到缩放后的RectF
    scaleMatrix.mapRect(srcRect)

    val finalOption = BitmapFactory.Options().apply {
        if (imageHeight > 0 && imageWidth > 0) {
            inPreferredConfig = Bitmap.Config.RGB_565
            inSampleSize = calculateInSampleSize(
                imageWidth,
                imageHeight,
                srcRect.width().toInt(),
                srcRect.height().toInt()
            )
        }
    }
    return BitmapFactory.decodeResource(resources, imageId, finalOption)
}

fun calculateInSampleSize(fromWidth: Int, fromHeight: Int, toWidth: Int, toHeight: Int): Int {
    var bitmapWidth = fromWidth
    var bitmapHeight = fromHeight
    if (fromWidth > toWidth|| fromHeight > toHeight) {
        var inSampleSize = 2
        // 计算最大的inSampleSize值,该值是2的幂,并保持原始宽高大于目标宽高
        while (bitmapWidth >= toWidth && bitmapHeight >= toHeight) {
            bitmapWidth /= 2
            bitmapHeight /= 2
            inSampleSize *= 2
        }
        return inSampleSize
    }
    return 1
}

自定义ViewGroupView代码

class MyImageView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr)


class MyRelativeLayout @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
    var mBitmap : Bitmap ?= null
    var mScaleMatrix = Matrix()
    var mDestRect = RectF()

    fun setBitmap(bitmap: Bitmap?) {
        mBitmap = bitmap
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        mBitmap?.let {
            canvas?.drawBitmap(it, mScaleMatrix, Paint().apply {
                isAntiAlias = true
            })
        }
    }
}

中心缩放

  • ValueAnimator动画的初始值为0,开始区域为外层MyRelativeLayout区域,结束区域为内层MyImageView区域,缩放区域在这两个区域大小之间变化
  • 缩放比例取决于当前缩放区域mDestRectBitmap宽高的最小宽高比
fun setDestRectCenterWithTranslate(srcRect: RectF, destRect: RectF, value : Float) {
    mDestRect = RectF(
        srcRect.left + (destRect.left - srcRect.left) * value,
        srcRect.top  + (destRect.top - srcRect.top) * value,
        srcRect.right + (destRect.right - srcRect.right) * value,
        srcRect.bottom + (destRect.bottom - srcRect.bottom) * value
    )
    val scale = Math.min(mDestRect.width() / mBitmap?.width!!, mDestRect.height() / mBitmap!!.height)
    val dx = (srcRect.width() - mBitmap?.width!! * scale) / 2
    val dy = (srcRect.height() - mBitmap?.height!! * scale) / 2
    mScaleMatrix.reset()
    mScaleMatrix.postScale(scale, scale)
    mScaleMatrix.postTranslate(dx, dy)
    invalidate()
}
  • 中心缩放效果图
    在这里插入图片描述

左上角缩放

  • 左上角不带平移缩放
fun setDestRectLeftTopNoTranslate(srcRect: RectF, destRect: RectF, value : Float) {
    mDestRect = RectF(
        srcRect.left + (destRect.left - srcRect.left) * value,
        srcRect.top + (destRect.top - srcRect.top) * value,
        srcRect.right + (destRect.right - srcRect.right) * value,
        srcRect.bottom + (destRect.bottom - srcRect.bottom) * value
    )
    val scale = Math.min(mDestRect.width() / mBitmap?.width!!, mDestRect.height() / mBitmap!!.height)
    mScaleMatrix.setScale(scale, scale)
    invalidate()
}
  • 左上角不带平移缩放效果图
    在这里插入图片描述

  • 左上角带平移缩放

fun setDestRectLeftTopWithTranslate(srcRect: RectF, destRect: RectF, value : Float) {
    mDestRect = RectF(
        srcRect.left + (destRect.left - srcRect.left) * value,
        srcRect.top + (destRect.top - srcRect.top) * value,
        srcRect.right + (destRect.right - srcRect.right) * value,
        srcRect.bottom + (destRect.bottom - srcRect.bottom) * value
    )
    val scale = Math.min(mDestRect.width() / mBitmap?.width!!, mDestRect.height() / mBitmap!!.height)
    val dx = ((srcRect.width() - destRect.width())/2) * value + (destRect.left - srcRect.left) * value
    val dy = ((srcRect.height() - destRect.height())/2) * value + (destRect.top - srcRect.top) * value
    mScaleMatrix.reset()
    mScaleMatrix.postScale(scale, scale)
    mScaleMatrix.postTranslate(dx, dy)
    invalidate()
}
  • 左上角带平移缩放效果图
    在这里插入图片描述

右上角缩放

  • 右上角不带平移缩放
fun setDestRectRightTopNoTranslate(srcRect: RectF, destRect: RectF, value : Float) {
    mDestRect = RectF(
        srcRect.left  + (destRect.left - srcRect.left) * value,
        srcRect.top + (destRect.top - srcRect.top) * value,
        srcRect.right + (destRect.right - srcRect.right) * value,
        srcRect.bottom + (destRect.bottom - srcRect.bottom) * value
    )
    val scale = Math.min(mDestRect.width() / mBitmap?.width!!, mDestRect.height() / mBitmap!!.height)
    val dx = srcRect.width() - mBitmap!!.width * scale
    val dy = 0f
    mScaleMatrix.reset()
    mScaleMatrix.postScale(scale, scale)
    mScaleMatrix.postTranslate(dx, dy)
    invalidate()
}
  • 右上角不带平移缩放效果图
    在这里插入图片描述
  • 右上角带平移缩放
fun setDestRectRightTopWithTranslate(srcRect: RectF, destRect: RectF, value : Float) {
    mDestRect = RectF(
        srcRect.left  + (destRect.left - srcRect.left) * value,
        srcRect.top + (destRect.top - srcRect.top) * value,
        srcRect.right + (destRect.right - srcRect.right) * value,
        srcRect.bottom + (destRect.bottom - srcRect.bottom) * value
    )
    val scale = Math.min(mDestRect.width() / mBitmap?.width!!, mDestRect.height() / mBitmap!!.height)
    val dx = ((srcRect.width() - destRect.width())/2) * value + (destRect.left - srcRect.left) * value
    val dy = ((srcRect.height() - destRect.height())/2) * value + (destRect.top - srcRect.top) * value
    mScaleMatrix.reset()
    mScaleMatrix.postScale(scale, scale)
    mScaleMatrix.postTranslate(dx, dy)
    invalidate()
}
  • 右上角不带平移缩放效果图
    在这里插入图片描述

左下角缩放

  • 左下角不带平移缩放
fun setDestRectLeftBottomNoTranslate(srcRect: RectF, destRect: RectF, value : Float) {
    mDestRect = RectF(
        srcRect.left + (destRect.left - srcRect.left) * value,
        srcRect.top  + (destRect.top - srcRect.top) * value,
        srcRect.right + (destRect.right - srcRect.right) * value,
        srcRect.bottom + (destRect.bottom - srcRect.bottom) * value
    )
    val scale = Math.min(mDestRect.width() / mBitmap?.width!!, mDestRect.height() / mBitmap!!.height)
    val dx = 0f
    val dy = srcRect.height() - mBitmap?.height!! * scale
    mScaleMatrix.reset()
    mScaleMatrix.postScale(scale, scale)
    mScaleMatrix.postTranslate(dx, dy)
    invalidate()
}
  • 左下角不带平移缩放效果图
    在这里插入图片描述
  • 左下角平移缩放
fun setDestRectLeftBottomWithTranslate(srcRect: RectF, destRect: RectF, value : Float) {
    mDestRect = RectF(
        srcRect.left + (destRect.left - srcRect.left) * value,
        srcRect.top  + (destRect.top - srcRect.top) * value,
        srcRect.right + (destRect.right - srcRect.right) * value,
        srcRect.bottom + (destRect.bottom - srcRect.bottom) * value
    )
    val scale = Math.min(mDestRect.width() / mBitmap?.width!!, mDestRect.height() / mBitmap!!.height)
    val dx = ((srcRect.width() - destRect.width())/2 )* value
    val dy = ((srcRect.height() - destRect.height())/2 )* value + (destRect.bottom - srcRect.bottom) * value + (srcRect.height()- mBitmap?.height!! * scale)
    mScaleMatrix.reset()
    mScaleMatrix.postScale(scale, scale)
    mScaleMatrix.postTranslate(dx, dy)
    invalidate()
}
  • 左下角平移缩放效果图
    在这里插入图片描述

右下角缩放

  • 右下角不带平移缩放
fun setDestRectRightBottomNoTranslate(srcRect: RectF, destRect: RectF, value : Float) {
    mDestRect = RectF(
        srcRect.left + (destRect.left - srcRect.left) * value,
        srcRect.top  + (destRect.top - srcRect.top) * value,
        srcRect.right + (destRect.right - srcRect.right) * value,
        srcRect.bottom + (destRect.bottom - srcRect.bottom) * value
    )
    val scale = Math.min(mDestRect.width() / mBitmap?.width!!, mDestRect.height() / mBitmap!!.height)
    val dx = srcRect.width() - mBitmap!!.width * scale
    val dy = srcRect.height() - mBitmap?.height!! * scale
    mScaleMatrix.reset()
    mScaleMatrix.postScale(scale, scale)
    mScaleMatrix.postTranslate(dx, dy)
    invalidate()
}
  • 右下角不带平移缩放效果图

在这里插入图片描述

  • 右下角平移缩放
fun setDestRectRightBottomWithTranslate(srcRect: RectF, destRect: RectF, value : Float) {
    mDestRect = RectF(
        srcRect.left + (destRect.left - srcRect.left) * value,
        srcRect.top  + (destRect.top - srcRect.top) * value,
        srcRect.right + (destRect.right - srcRect.right) * value,
        srcRect.bottom + (destRect.bottom - srcRect.bottom) * value
    )
    val scale = Math.min(mDestRect.width() / mBitmap?.width!!, mDestRect.height() / mBitmap!!.height)
    val dx = ((srcRect.width() - destRect.width())/2 )* value
    val dy = ((srcRect.height() - destRect.height())/2 )* value + (destRect.bottom - srcRect.bottom) * value + (srcRect.height()- mBitmap?.height!! * scale)
    mScaleMatrix.reset()
    mScaleMatrix.postScale(scale, scale)
    mScaleMatrix.postTranslate(dx, dy)
    invalidate()
}
  • 右下角平移缩放效果图
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/659318.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据要素的大海,如何流向千行百业?

文&#xff5c;白 鸽 编&#xff5c;王一粟 当数智化的风吹向银行业&#xff0c;是从底层数据的融合开始的。 在银行风控场景中&#xff0c;一个人想要进行风险投资或借贷&#xff0c;银行往往会评估这个人的信贷和风控策略。在以往的办理模式中&#xff0c;会需要办理人…

Java设计模式-活动对象与访问者

活动对象 Java设计模式中&#xff0c;活动对象是指一个对象始终处于活动的状态&#xff0c;该对象包括一个线程安全的数据结构以及一个活跃的执行线程。 如上所示&#xff0c;ActiveCreature类的构造函数初始化一个线程安全的数据结构&#xff08;阻塞队列&#xff09;、初始化…

微信小程序毕业设计-农场驿站平台系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

8086 汇编笔记(二):寄存器(内存访问)

一、内存中字的存储 字单元的概念&#xff1a;字单元&#xff0c;即存放一个字型数据(16 位)的内存单元&#xff0c;由两个地址连续的内存单元组成 由上一章学习可知&#xff1a;高地址内存单元中存放字型数据的高位字节&#xff0c;低地址内存单元中存放字型数据的低位字节 …

数字资产革命:Web3带来的新商业机会

随着区块链技术的不断发展和普及&#xff0c;数字资产正逐渐成为全球范围内的热门话题。作为区块链技术的重要应用之一&#xff0c;Web3正在带来一场数字资产的革命&#xff0c;为传统商业模式带来了全新的机遇和挑战。本文将深入探讨数字资产革命与Web3的关系&#xff0c;探索…

数据分析之统计学基础

数据分析是现代企业和科研中不可或缺的一部分&#xff0c;而统计学是数据分析的基石。在本篇博客中&#xff0c;我们将介绍统计学的基础知识&#xff0c;涵盖数据类型、描述性统计&#xff08;集中趋势、离散程度和偏差程度&#xff09;&#xff0c;并通过代码实例加以说明。 …

如何批量结构化分汇多工作表sheet?

目录 一、如遇合并表格&#xff0c;注意结构化二、确认主键&#xff0c;合并所有文件数据三、sheet2同理四、案例总结 如果遇到这样情形&#xff0c;多文件夹多文件&#xff0c;多工作表的分汇场景&#xff1b;可以参考以下方法解决。 一、如遇合并表格&#xff0c;注意结构…

汇编原理(四)[BX]和loop指令

loop&#xff1a;循环 误区&#xff1a;在编译器里写代码和在debug里写代码是不一样的&#xff0c;此时&#xff0c;对于编译器来说&#xff0c;就需要用到[bx] [bx]: [bx]同样表示一个内存单元&#xff0c;他的偏移地址在bx中&#xff0c;比如下面的指令 move bx, 0 move ax,…

IAR9.30安装和注册相关

下载解压licpatcher64工具&#xff0c;把licpatcher64.exe拷贝到IAR的安装目录中双击运行。 示例IAR9.30.1默认安装如下如下&#xff0c;一共三个分别拷贝运行&#xff0c;不要遗漏。 C:\Program Files\IAR Systems\Embedded Workbench 9.1\arm\bin C:\Program Files\IAR Syst…

一书读懂Python全栈安全,剑指网络空间安全

写在前面 通过阅读《Python全栈安全/网络空间安全丛书》&#xff0c;您将能够全面而深入地理解Python全栈安全的广阔领域&#xff0c;从基础概念到高级应用无一遗漏。本书不仅详细解析了Python在网络安全、后端开发、数据分析及自动化等全栈领域的安全实践&#xff0c;还紧密贴…

系统与软件工程软件测试过程

系统与软件工程 软件测试 测试过程 &#xff1b;对应的国标是GB/T 38634.4 2020 &#xff0c;该标准的范围规定适应用于治理、管理和实施任何组织,项目或较小规模测试活动的软件测试的测试过程,定义了软件测试通用过程,给出了描述过程的支持信息图表。 一 术语和定义 1.1实测…

力扣hot100:23. 合并 K 个升序链表

23. 合并 K 个升序链表 这题非常容易想到归并排序的思路&#xff0c;俩升序序列合并&#xff0c;可以使用归并的方法。 不过这里显然是一个多路归并排序&#xff1b;包含多个子数组的归并算法&#xff0c;这可以让我们拓展归并算法的思路。 假设n是序列个数&#xff0c;ni是…

这么多不同接口的固态硬盘,你选对了嘛!

固态硬盘大家都不陌生,玩游戏、办公存储都会用到。如果自己想要给电脑或笔记本升级下存储,想要存储更多的文件,该怎么选购不同类型的SSD固态盘呐,下面就来认识下日常使用中常见的固态硬盘。 固态硬盘(Solid State Drive, SSD)作为数据存储技术的革新力量,其接口类型的选…

5.25.6 深度学习在放射图像中检测和分类乳腺癌病变

计算机辅助诊断 (CAD) 系统使用数字化乳房 X 线摄影图像并识别乳房中存在的异常情况。深度学习方法从有限数量的专家注释数据中学习图像特征并预测必要的对象。卷积神经网络&#xff08;CNN&#xff09;在图像检测、识别和分类等各种图像分析任务中的性能近年来表现出色。本文提…

VSCode连接远程服务器使用jupyter报错问题解决

目录 一. 问题描述二. jupyter环境确认三. 插件安装 一. 问题描述 经常会遇到一种问题就是, VSCode连接远程服务器, 上次jupyter notebook 还用的好好的, 下次打开就显示找不到内核了. 今天提供了全套解决方案, 帮大家迅速解决环境问题. 二. jupyter环境确认 首先进入自己需…

OPPO Reno12系列发布:用它玩游戏比凉茶还要“凉”

在这个智能手机市场日新月异的时代&#xff0c;每一次新品发布都牵动着无数科技爱好者的心。最近&#xff0c;OPPO官微传来好消息&#xff0c;即将推出的OPPO Reno12系列不仅搭载了顶尖的旗舰芯片&#xff0c;还与联发科天玑强强联手&#xff0c;进行了深度的优化调校&#xff…

【408真题】2009-21

“接”是针对题目进行必要的分析&#xff0c;比较简略&#xff1b; “化”是对题目中所涉及到的知识点进行详细解释&#xff1b; “发”是对此题型的解题套路总结&#xff0c;并结合历年真题或者典型例题进行运用。 涉及到的知识全部来源于王道各科教材&#xff08;2025版&…

Honor of Kings 2024.03.29 Ban for 3 day

我又被举报消极然后禁赛 都说了别选蔡文姬&#xff0c;对面三个肉&#xff0c;非要选个软辅助 吐槽下这游戏策划&#xff1a;游戏体验感越来越差&#xff0c;公正也很差 对说了对面4个法师&#xff0c;就是不出魔抗&#xff0c;把把都是0-N开局&#xff0c;到底谁消极啊&#x…

apexcharts数据可视化之圆环柱状图

apexcharts数据可视化之圆环柱状图 有完整配套的Python后端代码。 本教程主要会介绍如下图形绘制方式&#xff1a; 基础圆环柱状图多组数据圆环柱状图图片背景自定义角度渐变半个圆环图虚线圆环图 基础圆环图 import ApexChart from react-apexcharts;export function Cir…

基于jeecgboot-vue3的Flowable流程-我的任务(一)

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、首先可以用现成生成代码的前端来做这个&#xff0c;只要做一些调整就可以了&#xff0c;这样利用现有的一些模板可以快速构建我的任务&#xff0c;否则vue2与vue3相差太大&#xff0c;移…