(UI)Android自定义图片裁剪

这篇具有很好参考价值的文章主要介绍了(UI)Android自定义图片裁剪。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

具体UI效果如下:

android 自定义view实现图片裁剪,ui,android

思路

绘制5个rect,其中四个为半透明深色背景,一个为透明背景的裁剪内容框

之前也考虑过用region,但是自测的时候,发现两个region之间颜色会相互影响,可能是我代码问题(有了解的小伙伴可以指导一下哈),就用了5个Rect来绘制开发效率会更高一些。

2023年8月24号更新:

考虑到图片裁剪定位思路比较复杂,这里把最新的定位代码也贴上来!

具体代码如下:

package com.xingzhi.customview

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View


/**
 *
 *Date:2023/5/13
 *Time:20:08
 *author:wushengqi
 *
 * https://blog.csdn.net/qq_39312146/article/details/129053307
 *
 * https://blog.csdn.net/yihonglvyu1/article/details/122089901
 *
 * Region:
 * https://blog.csdn.net/qq_27061049/article/details/104534867
 *
 * 多点触控:
 * https://blog.51cto.com/u_15749390/5570836
 */
class ImageCropView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
    View(context, attrs, defStyleAttr, defStyleRes) {

    companion object{
        val TAG = ImageCropView::class.java.simpleName
    }

    init {
        init()
    }

    constructor(context: Context?):this(context, attributeSet = null){

    }

    constructor(context: Context?, attributeSet: AttributeSet?) : this(context, attributeSet, defStyleAttr = 0) {

    }

    constructor(context: Context?, attributeSet: AttributeSet?, defStyleAttr:Int) : this(context, attributeSet, defStyleAttr, defStyleRes = 0){

    }

    private lateinit var mBitmap: Bitmap//图片
    private var mBitmapRect:Rect? = null//图片绘制大小
    private var mCropRect:Rect? = null//裁剪框
    private var mPaint:Paint? = null //画笔
    private var mBgPaint:Paint? = null //裁剪框之外的颜色
    private var mCenterPaint:Paint? = null //裁剪框中心的颜色
    private var mCropX:Float = 0.0f//裁剪框的X坐标
    private var mCropY:Float = 0.0f//裁剪框的Y坐标
    private var mCropWidth:Float = 0F//裁剪框的宽度
    private var mCropHeight:Float = 0F//裁剪框的高度
    private var mOldX:Float = 0.0f//上一次触摸的X坐标
    private var mOldY:Float = 0.0f//上一次触摸的Y坐标
    private var mOldCropX:Float = 0.0f//上一次裁剪框的X坐标
    private var mOldCropY:Float = 0.0f//上一次裁剪框的Y坐标

    private var drawBitmapHeight = 0F
    private var drawBitmapWidth = 0F
    private var drawBitmapLeft = 0F
    private var drawBitmapTop = 0F
    private var drawBitmapRight = 0F
    private var drawBitmapBottom = 0F

    private var firstDraw = true
    private var initCropWidth = 0
    private var initCropHeight = 0

    private fun init() {
        mPaint = Paint()
        mPaint!!.style = Paint.Style.STROKE
        mPaint!!.strokeWidth = 4f
        mPaint!!.color = Color.WHITE

        mBgPaint = Paint()
        mBgPaint!!.style = Paint.Style.FILL
        mBgPaint!!.color = Color.parseColor("#AA000000")

        mCenterPaint = Paint()
        mCenterPaint!!.style = Paint.Style.FILL
        mCenterPaint!!.color = Color.parseColor("#00000000")

    }

    //设置图片
    fun setBitmap(bitmap: Bitmap) {
        mBitmap = bitmap
        invalidate()
    }

    /**
     * 绘制bitmap的基准
     */
    enum class FillBitmapStand{
        WIDTH_FILL, //填充满控件宽度
        HEIGHT_FILL //填充满控件高度
    }

    var mFillStand = FillBitmapStand.WIDTH_FILL

    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (mBitmap != null) {

            Log.d(TAG, "onDraw")
            val bitmapWidth = mBitmap.width
            val bitmapHeight = mBitmap.height

            // 如果图片的高度和控件实际高度比大于宽度比,则高度填充满整个屏幕,宽度按比例缩放
            if (bitmapHeight.toFloat()/ measuredHeight > bitmapWidth / measuredWidth){
                drawBitmapHeight = measuredHeight.toFloat()
                drawBitmapWidth = ((bitmapWidth * ( measuredHeight.toFloat()/bitmapHeight)))

                drawBitmapLeft = (measuredWidth - drawBitmapWidth) / 2
                drawBitmapTop = 0F

                mFillStand = FillBitmapStand.HEIGHT_FILL

            }else{
                drawBitmapWidth = measuredWidth.toFloat()
                drawBitmapHeight = (bitmapHeight * (measuredWidth.toFloat()/bitmapWidth))

                drawBitmapLeft = 0F
                drawBitmapTop = (measuredHeight - drawBitmapHeight)/ 2
                mFillStand = FillBitmapStand.WIDTH_FILL
            }

            //绘制图片
            this.drawBitmapRight = drawBitmapLeft + drawBitmapWidth
            this.drawBitmapBottom =  drawBitmapTop + drawBitmapHeight
            mBitmapRect = Rect(
                drawBitmapLeft.toInt(),
                drawBitmapTop.toInt(),
                drawBitmapRight.toInt(),
                drawBitmapBottom.toInt(),
            )

            //绘制图片
            canvas.drawBitmap(mBitmap!!, null, mBitmapRect!!, mPaint)

            //如果裁剪框位置没有初始化,则将裁剪框位置置于中心
            if (firstDraw){
                Log.e(TAG, "图片宽:$drawBitmapWidth  高:$drawBitmapHeight")
                if(drawBitmapWidth/mCropWidth > drawBitmapHeight/mCropHeight){
                    mCropWidth = mCropWidth * (drawBitmapHeight - 100)/ mCropHeight
                    mCropHeight = drawBitmapHeight - 100
                }else{
                    mCropHeight = mCropHeight * (drawBitmapWidth - 100) / mCropWidth
                    mCropWidth = drawBitmapWidth - 100
                }

                Log.e(TAG, "当前裁剪框宽:$mCropWidth  高:$mCropHeight")
                mCropY = (measuredHeight - mCropHeight)/2
                mCropX = (measuredWidth - mCropWidth)/ 2
                firstDraw = false
            }

            //绘制裁剪框
            mCropRect = Rect(mCropX.toInt(),
                mCropY.toInt(),
                (mCropX + mCropWidth).toInt(),
                (mCropY + mCropHeight).toInt()
            )

            //绘制裁剪框部分
            canvas.drawRect(mCropRect!!, mCenterPaint!!)

            //绘制非裁剪框部分
            var topRect = Rect(0, 0, measuredWidth, mCropY.toInt())
            var leftRect = Rect(0, mCropY.toInt(), mCropX.toInt(), (mCropHeight + mCropY).toInt())
            var RightRect = Rect((mCropX + mCropWidth).toInt(), mCropY.toInt(), measuredWidth, (mCropHeight + mCropY).toInt())
            var bottomRect = Rect(0, (mCropHeight + mCropY).toInt(), measuredWidth, measuredHeight)
            canvas.drawRect(topRect, mBgPaint!!)
            canvas.drawRect(leftRect, mBgPaint!!)
            canvas.drawRect(RightRect, mBgPaint!!)
            canvas.drawRect(bottomRect, mBgPaint!!)

        }
    }

    private fun drawRegion(canvas: Canvas, rgn: Region, paint: Paint) {
        val iter = RegionIterator(rgn)
        val r = Rect()
        while (iter.next(r)) {
            canvas.drawRect(r, paint)
        }
    }

    /**
     * 设置裁剪框宽高
     */
    fun setCropSize(width:Int, height: Int){
        Log.e(TAG, "原始宽:$width 高:$height")
        initCropWidth = width
        initCropHeight = height

        mCropHeight = height.toFloat()
        mCropWidth = width.toFloat()
        firstDraw = true
        invalidate()

    }

    fun getViewSize():Double{
        return Math.sqrt((drawBitmapWidth * drawBitmapWidth + drawBitmapHeight * drawBitmapHeight).toDouble())
    }

    /**
     * 绘制缩放裁剪框
     */
    fun drawScaleStateCropRect(event: MotionEvent){

        //计算距离
        val nowDistance = distance(event)
        val diffDistance = nowDistance - mOldDistance
        mOldDistance = nowDistance

        //控件的对角线长度
        val viewSize = getViewSize()

        //计算缩放率 乘以2提高缩放效率
        var scale = diffDistance / viewSize * 2
        scale += 1
        var oldCropHeight = mCropHeight
        var oldCropWidth = mCropWidth

        mCropHeight = (mCropHeight * scale).toFloat()
        mCropWidth = (mCropWidth * scale).toFloat()

        mOldCropY = mCropY
        mOldCropX = mCropX

        mCropX = mCropX - (mCropWidth - oldCropWidth)/2
        mCropY = mCropY - (mCropHeight - oldCropHeight)/ 2


        //如果超出边界则不重绘,傻逼了,这个是最简单的算法了,搞了半天
        if (mCropX <= drawBitmapLeft || mCropX + mCropWidth > drawBitmapRight || mCropY <= drawBitmapTop || mCropY + mCropHeight> drawBitmapBottom){
            mCropX = mOldCropX
            mCropY = mOldCropY
            mCropHeight = oldCropHeight
            mCropWidth = oldCropWidth
            return
        }

        /**
         控制缩放边界失败,这个是个傻逼算法,留着以做教训

        if (mCropX <= drawBitmapLeft){
            mCropX = drawBitmapLeft

            var nowWidth = Math.abs(mCropX - mOldCropX) * 2 + oldCropWidth;
            Log.d(TAG, "当前宽度:${nowWidth}")

            var nowScale = nowWidth/oldCropWidth;

            resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
            mCropX = drawBitmapLeft
            return

        }
        if (mCropX + mCropWidth > drawBitmapRight){
            mCropX = drawBitmapRight - mCropWidth

            var nowWidth = Math.abs(-mCropX + mOldCropX) * 2 + oldCropWidth;
            Log.d(TAG, "当前宽度:${nowWidth}")

            var nowScale = nowWidth/oldCropWidth;

            resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
            mCropX = drawBitmapRight - mCropWidth
            return
        }

        if (mCropY <= drawBitmapTop){
            mCropY = drawBitmapTop

            var nowHeight = Math.abs(mCropY - mOldCropY) * 2 + oldCropHeight;
            Log.d(TAG, "当前高度:${nowHeight}")

            var nowScale = nowHeight/oldCropHeight;

            resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
            mCropY = drawBitmapTop
            return
        }
        if (mCropY + mCropHeight> drawBitmapBottom){
            mCropY = drawBitmapBottom - mCropHeight

            var nowHeight = (mCropY - mOldCropY) * 2 + oldCropHeight;
            Log.d(TAG, "当前高度:${nowHeight}")

            var nowScale = nowHeight/oldCropHeight;

            resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
            mCropY = drawBitmapBottom - mCropHeight
            return
        }*/
        invalidate()
    }

    fun resetScaleRect(scale: Float, oldCropWidth: Float, oldCropHeight:Float){
        Log.e(TAG, "重置初始值 scale:${scale}  oldCropWidth:${oldCropWidth} oldCropHeight:${oldCropHeight}" )
        mCropHeight = (oldCropHeight * scale)
        mCropWidth = (oldCropWidth * scale)
        Log.e(TAG, "重置前位置  mCropX:${mOldCropX} mCropY:${mOldCropX}" )
        Log.e(TAG, "重置后宽度:${mCropWidth} 高度:${mCropHeight}")
        mCropX = mOldCropX
        mCropY = mOldCropY

        mCropX = mCropX - (mCropWidth - oldCropWidth)/2
        mCropY = mCropY - (mCropHeight - oldCropHeight)/ 2
        Log.e(TAG, "重置后位置  mCropX:${mCropX} mCropY:${mCropY}" )

        if (mCropX < drawBitmapLeft){
            mCropX = drawBitmapLeft
        }

        mOldCropY = mCropY
        mOldCropX = mCropX
        invalidate()
    }

    enum class ActionState{
        MOVE_STATE, SCALE_STATE
    }

    var canMoveAction = false
    var mActionState = ActionState.MOVE_STATE
    var mOldDistance: Double = 0.0 //上次两点之间的距离

    override fun onTouchEvent(event: MotionEvent): Boolean {

        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                mActionState = ActionState.MOVE_STATE
                mOldX = event.x
                mOldY = event.y
                mOldCropX = mCropX
                mOldCropY = mCropY

                canMoveAction = mOldX > mCropX && mOldX < mCropX + mCropWidth  && mOldY > mCropY && mOldY < mCropY + mCropHeight
            }
            MotionEvent.ACTION_MOVE -> {
                //裁剪框处于移动状态
                if (canMoveAction && mActionState == ActionState.MOVE_STATE){
                    val dx = event.getX(0) - mOldX
                    val dy = event.getY(0) - mOldY

                    //保存裁剪框的位置
                    mCropX = mOldCropX + dx
                    mCropY = mOldCropY + dy

                    if (mCropX <= drawBitmapLeft){
                        mCropX = drawBitmapLeft
                    }
                    if (mCropX + mCropWidth > drawBitmapRight){
                        mCropX = drawBitmapRight - mCropWidth
                    }

                    if (mCropY <= drawBitmapTop){
                        mCropY = drawBitmapTop
                    }
                    if (mCropY + mCropHeight> drawBitmapBottom){
                        mCropY = drawBitmapBottom - mCropHeight
                    }

                    invalidate()
                }

                if (canMoveAction && mActionState == ActionState.SCALE_STATE){
                    drawScaleStateCropRect(event)
                }

            }
            MotionEvent.ACTION_UP ->{
                canMoveAction = false
                mActionState = ActionState.MOVE_STATE
            }
            MotionEvent.ACTION_POINTER_DOWN ->{
                canMoveAction = true
                mActionState = ActionState.SCALE_STATE
                mOldDistance = distance(event)
            }
            MotionEvent.ACTION_POINTER_UP ->{
                mActionState = ActionState.MOVE_STATE
                canMoveAction = false
            }
        }
        return true
    }

    //裁剪图片
    fun crop(): Bitmap? {
        val scale = mBitmap.height / drawBitmapHeight
        val width = (mCropWidth * scale).toInt()
        val height = (mCropHeight * scale).toInt()

        /// 裁剪点相对于View中图片的位置,跟实际图片位置是两回事
        val relativeX = mCropX - drawBitmapLeft
        val relativeY = mCropY - drawBitmapTop

        return Bitmap.createBitmap(mBitmap!!, (relativeX * scale).toInt(), (relativeY * scale).toInt(), width, height)
    }

    // 计算两个触摸点之间的距离
    private fun distance(event:MotionEvent ): Double {
        var x = event.getX(0) - event.getX(1);
        var y = event.getY(0) - event.getY(1);
        return Math.sqrt((x * x + y * y).toDouble());
    }

}

 文章来源地址https://www.toymoban.com/news/detail-771444.html

 

 

 

到了这里,关于(UI)Android自定义图片裁剪的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android 自定义view中根据状态修改drawable图片

    原文地址:Android 自定义view中根据状态修改drawable图片 - Stars-One的杂货小窝 本文涉及知识点: Android里的selector图片使用 底部导航栏的使用 自定义view的步骤了解 建议有以上基础有助于帮助你理解本篇文章.... 起因,由于UI那边的实现,不是按照的Material Design风格设计的,设计的底部

    2024年02月13日
    浏览(47)
  • UI系列一Android多子view嵌套通用解决方案

    百度App在17年的版本中实现2个子view嵌套滚动,用于Feed落地页(webview呈现文章详情 + recycle呈现Native评论)。原理是在外层提供一个UI容器(我们称之为”联动容器”)处理WebView和Recyclerview连贯嵌套滚动。 当时的联动容器对子view限制比较大,仅支持WebView和Recyclerview进行联动滚动

    2024年04月16日
    浏览(41)
  • Android-高级-UI-进阶之路-(二)-深入理解-Android-8-0-View-触摸事件分发机制,查漏补缺

    我们看到内部又调用了父类 dispatchTouchEvent 方法, 所以最终是交给 ViewGroup 顶级 View 来处理分发了。 顶级 View 对点击事件的分发过程 在上一小节中我们知道了一个事件的传递流程,这里我们就大致在回顾一下。首先点击事件到达顶级 ViewGroup 之后,会调用自身的 dispatchTouchE

    2024年04月14日
    浏览(71)
  • Android 动态代码设置view宽高参数,运行后UI大小没有改变问题

         日常开发中遇到一个需求,就是根据业务逻辑,动态改变一个view控件的大小。这种需求也是比较常见的,但是小白比较容易遇到一个小问题,就是代码重新设置了view的宽高大小,运行后发现view没有发生改变。          如下图,1,横屏 2,正方形,3,竖屏      

    2024年02月16日
    浏览(48)
  • android自定义来电秀UI

    简单来电秀功能,效果如图: 底部附上demo  一、新建一个 PhoneCallService服务,在服务中监听来电等状态,且控制UI显示 二、在MainActivity中设置应用为默认来电主题,与权限设置 三、增加一个来电展示的自定义UI页面PhoneCallActivity,可根据需求更改 四、来电页面UI中布局文件

    2024年01月15日
    浏览(40)
  • Android的UI---ZoomControls放大缩小图片

    这里面,如果将setIsZoomInEnabled()方法设置为false,那么这个放大的按钮就变成了灰色,不能用了,其实这个控件就是两个按钮而已,只是有外观,没有功能,如果你要放大图片或者缩小图片,还是要在监听事件中实现 开始看代码 main.xml文件: ?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"? Linea

    2024年04月12日
    浏览(40)
  • Android自定义View之游戏摇杆键盘实现(一),快手android面试经验

    public class RemoteViewBg { private Bitmap bitmapBg; public RemoteViewBg(Bitmap bitmap) { bitmapBg = bitmap; } //背景的绘图函数 public void draw(Canvas canvas, Paint paint, Rect src0 ,Rect dst0 ) { canvas.drawBitmap(bitmapBg, src0, dst0, paint); } } 重写系统的触摸时间,判断触摸点在背景范围内还是背景范围外 @Override public b

    2024年04月12日
    浏览(46)
  • Android技术分享| Bugly 应用升级自定义UI

    最近项目里的采用免费的Bugly应用升级功能,由于默认的 UI 非常的简陋且与项目整体风格不搭,所以需要自定义UI,本篇文章记录在实现过程中的一些注意事项。根据官方文档可知,自定义升级界面有以下两种方式。 一、固定控件ID 这种方式比较简单,只需要自己编写布局,

    2024年02月02日
    浏览(44)
  • Android自定义View之游戏摇杆键盘实现(一)

    public class RemoteViewBg { private Bitmap bitmapBg; public RemoteViewBg(Bitmap bitmap) { bitmapBg = bitmap; } //背景的绘图函数 public void draw(Canvas canvas, Paint paint, Rect src0 ,Rect dst0 ) { canvas.drawBitmap(bitmapBg, src0, dst0, paint); } } 重写系统的触摸时间,判断触摸点在背景范围内还是背景范围外 @Override public b

    2024年04月17日
    浏览(55)
  • (二) 盘古UI,全网独创,较为全面的自定义Android UI框架,绝对帮助你快速开发!(盘古导航栏-PanguNavBar)

    (二) 盘古UI,较为全面的自定义UI框架,帮助你绝对的快速开发!(长期维护中) demo地址,点击查看github 1, 样例展示图 2, 介绍 个性化导航栏,标题栏,可以灵活设置和配置各种属性和事件! 下面直接上属性列表: attr 属性 对应的方法 method 介绍 introduction pangu_title_mid setMidTitle(String title)

    2024年04月14日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包