简单可拖动自定义View+重绘

可移动、可改变大小的矩形

需求:
1、矩形样式
2、可拖动
3、拖动时颜色改变
4、设置拖动边界
5、拖动四个角、可改变矩形大小

在这里插入图片描述

实现代码:

/** 
 * Android 自定义View可拖动移动位置及边缘拉伸放大缩小 
 * 矩形 
 */ 
 
class MyRectangleView : View, View.OnTouchListener {
   
     
 
    private var listener: RudderListener? = null // 事件回调接口 
    var mPaintK: Paint? = null//默认边框 
    var mPaintAngel: Paint? = null//四个角 
 
    //是否开启抗锯齿 
    private val antiAlias = true 
    private val mArcWidth = 1f 
 
    //view中心点 
    var centerIndexX = 0 
    var centerIndexY = 0 
 
    //默认宽高 
    var viewHeight = 200 
    var viewWidth = 250 
 
    //四个角的宽高 
    var angelWidth = 20 
    var angelinterval = 1//边角距离外层的距离 
 
    //双层中间隔 
    var interval = 5 
 
    var downX = 0 
    var downY = 0 
    var downRawX = 0 
    var downRawY = 0 
    var isMove = true 
 
 
    var aviewWidth = 300 
    var aviewHeight = 300 
    var originalViewWidth = aviewWidth 
    var originalViewHeight = aviewHeight 
    var mFatherViewWidth = 720 
    var mFatherViewHeight = 1080 
 
    var mContext: Context? = null 
    private var direction = "Center" 
 
    //view的原始尺寸 
    var originalLeft = 0 
    var originalRight = 0 
    var originalTop = 0 
    var originalBottom = 0 
 
    var index=0 
 
 
 
    constructor(context: Context?) : super(context) 
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
   
     
        this.mContext = context 
        this.keepScreenOn = true 
        initPaint2(Color.WHITE) 
        isFocusable = true 
        isFocusableInTouchMode = true 
        setOnTouchListener(this) 
    } 
 
    constructor(context: Context?, mFatherViewWidth: Int, mFatherViewHeight: Int,num:Int) : super(context) {
   
     
        this.mFatherViewWidth = mFatherViewWidth 
        this.mFatherViewHeight = mFatherViewHeight 
        this.mContext = context 
        this.keepScreenOn = true 
        this.index=num 
        initPaint2(Color.WHITE) 
        //根据选中状态设置默认颜色 
        /*if(ParamsUtils.listLocation[index].check==1){ 
            initPaint2(resources.getColor(R.color.tv_color_00)) 
        }else{ 
            initPaint2(Color.WHITE) 
        }*/ 
        isFocusable = true 
        isFocusableInTouchMode = true 
        setOnTouchListener(this) 
    } 
 
    fun initPaint2(color:Int) {
   
     
        /** 
         * 默认边框 
         */ 
        mPaintK = Paint() 
        mPaintK!!.isAntiAlias = antiAlias 
        // 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE 
        mPaintK!!.style = Paint.Style.STROKE 
        // 设置画笔粗细 
        mPaintK!!.strokeWidth = 2 * mArcWidth 
        mPaintK!!.color = color 
        mPaintK!!.textAlign = Paint.Align.CENTER 
        /** 
         * 四个角 
         */ 
        mPaintAngel = Paint() 
        mPaintAngel!!.isAntiAlias = antiAlias 
        // 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE 
        mPaintAngel!!.style = Paint.Style.STROKE 
        // 设置画笔粗细 
        mPaintAngel!!.strokeWidth = 4 * mArcWidth 
        mPaintAngel!!.color = color 
        mPaintAngel!!.textAlign = Paint.Align.CENTER 
    } 
 
 
    /** 
     * 设置对应图形信息 
     */ 
    fun setGraphContext(width: Int, height: Int) {
   
     
        viewWidth = (width - 20).toInt() 
        viewHeight = (height - 20).toInt() 
        // 根据屏幕大小绘制 
        centerIndexX = aviewWidth / 2 
        centerIndexY = aviewHeight / 2 
    } 
 
    override fun onDraw(canvas: Canvas?) {
   
     
        super.onDraw(canvas) 
        setGraphContext(width, height) 
        drawRectangular(canvas) 
    } 
 
 
    /** 
     * 绘制矩形 
     */ 
    fun drawRectangular(canvas: Canvas?) {
   
     
        //绘制左边矩形 
        val pathB = Path() 
        pathB.moveTo( 
            (centerIndexX + viewWidth / 2).toFloat(), 
            (centerIndexY - viewHeight / 2).toFloat() 
        )//起点  暂定右上角为起点 
        pathB.lineTo( 
            (centerIndexX - viewWidth / 2).toFloat(), 
            (centerIndexY - viewHeight / 2).toFloat() 
        )//左上角 
        pathB.lineTo( 
            (centerIndexX - viewWidth / 2).toFloat(), 
            (centerIndexY + viewHeight / 2).toFloat() 
        )//左下角 
        pathB.lineTo( 
            (centerIndexX + viewWidth / 2).toFloat(), 
            (centerIndexY + viewHeight / 2).toFloat() 
        )//右下角 
        pathB.close() // 使这些点构成封闭的多边形 
        canvas!!.drawPath(pathB, mPaintK!!) 
        //绘制左边矩形 
        val pathS = Path() 
        pathS.moveTo( 
            (centerIndexX + viewWidth / 2 - interval).toFloat(), 
            (centerIndexY - viewHeight / 2 + interval).toFloat() 
        )//起点  暂定右上角为起点 
        pathS.lineTo( 
            (centerIndexX - viewWidth / 2 + interval).toFloat(), 
            (centerIndexY - viewHeight / 2 + interval).toFloat() 
        )//左上角 
        pathS.lineTo( 
            (centerIndexX - viewWidth / 2 + interval).toFloat(), 
            (centerIndexY + viewHeight / 2 - interval).toFloat() 
        )//左下角 
        pathS.lineTo( 
            (centerIndexX + viewWidth / 2 - interval).toFloat(), 
            (centerIndexY + viewHeight / 2 - interval).toFloat() 
        )//右下角 
        pathS.close() // 使这些点构成封闭的多边形 
        canvas!!.drawPath(pathS, mPaintK!!) 
        /** 
         * 四个角边框 
         */ 
        //右上角边框 
        canvas!!.drawLine( 
            (centerIndexX + viewWidth / 2 + angelinterval).toFloat(), 
            (centerIndexY - viewHeight / 2 - angelinterval).toFloat(), 
            (centerIndexX + viewWidth / 2 + angelinterval - angelWidth).toFloat(), 
            (centerIndexY - viewHeight / 2 - angelinterval).toFloat(), 
            mPaintAngel!! 
        ) 
        canvas!!.drawLine( 
            (centerIndexX + viewWidth / 2 + angelinterval).toFloat(), 
            (centerIndexY - viewHeight / 2 - angelinterval).toFloat(), 
            (centerIndexX + viewWidth / 2 + angelinterval).toFloat(), 
            (centerIndexY - viewHeight / 2 - angelinterval + angelWidth).toFloat(), 
            mPaintAngel!! 
        ) 
        //左上角边框 
        canvas!!.drawLine( 
            (centerIndexX - viewWidth / 2 - angelinterval).toFloat(), 
            (centerIndexY - viewHeight / 2 - angelinterval).toFloat(), 
            (centerIndexX - viewWidth / 2 - angelinterval + angelWidth).toFloat(), 
            (centerIndexY - viewHeight / 2 - angelinterval).toFloat(), 
            mPaintAngel!! 
        ) 
        canvas!!.drawLine( 
            (centerIndexX - viewWidth / 2 - angelinterval).toFloat(), 
            (centerIndexY - viewHeight / 2 - angelinterval).toFloat(), 
            (centerIndexX - viewWidth / 2 - angelinterval).toFloat(), 
            (centerIndexY - viewHeight / 2 - angelinterval + angelWidth).toFloat(), 
            mPaintAngel!! 
        ) 
        //左下角边框 
        canvas!!.drawLine( 
            (centerIndexX - viewWidth / 2 - angelinterval).toFloat(), 
            (centerIndexY + viewHeight / 2 + angelinterval).toFloat(), 
            (centerIndexX - viewWidth / 2 - angelinterval).toFloat(), 
            (centerIndexY + viewHeight / 2 + angelinterval - angelWidth).toFloat(), 
            mPaintAngel!! 
        ) 
        canvas!!.drawLine( 
            (centerIndexX - viewWidth / 2 - angelinterval).toFloat(), 
            (centerIndexY + viewHeight / 2 + angelinterval).toFloat(), 
            (centerIndexX - viewWidth / 2 - angelinterval + angelWidth).toFloat(), 
            (centerIndexY + viewHeight / 2 + angelinterval).toFloat(), 
            mPaintAngel!! 
        ) 
        //右下角 
        canvas!!.drawLine( 
            (centerIndexX + viewWidth / 2 + angelinterval).toFloat(), 
            (centerIndexY + viewHeight / 2 + angelinterval).toFloat(), 
            (centerIndexX + viewWidth / 2 + angelinterval).toFloat(), 
            (centerIndexY + viewHeight / 2 + angelinterval - angelWidth).toFloat(), 
            mPaintAngel!! 
        ) 
        canvas!!.drawLine( 
            (centerIndexX + viewWidth / 2 + angelinterval).toFloat(), 
            (centerIndexY + viewHeight / 2 + angelinterval).toFloat(), 
            (centerIndexX + viewWidth / 2 + angelinterval - angelWidth).toFloat(), 
            (centerIndexY + viewHeight / 2 + angelinterval).toFloat(), 
            mPaintAngel!! 
        ) 
    } 
 
    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
   
     
        val action = event!!.action 
        if (action == MotionEvent.ACTION_DOWN) {
   
     
            downX = event.x.toInt() 
            downY = event.y.toInt() 
            downRawX = event.rawX.toInt() 
            downRawY = event.rawY.toInt() 
            originalLeft = v!!.left 
            originalBottom = v!!.bottom 
            originalTop = v!!.top 
            originalRight = v!!.right 
            direction = getDirection(v, downX, downY) 
            Log.i( 
                "Move3==", 
                "downRawX==" + downRawX + "downRawY==" + downRawY + "originalBottom==" + originalBottom + "originalRight==" + originalRight 
            ) 
            Choose()//选中变色 
        } 
        //处理拖动事件 
        contactDrag(v!!, event, action) 
        invalidate() 
        return true 
    } 
 
    override fun isFocused(): Boolean {
   
     
        Log.i("isFocused==",super.isFocused().toString()) 
        return super.isFocused() 
    } 
    /** 
     * 获取触摸位置 
     */ 
    fun getDirection(v: View, x: Int, y: Int): String {
   
     
        var reDrawRatio = 0.3 
        var reDrawX = (aviewWidth * reDrawRatio).toInt() 
        var reDrawY = (aviewHeight * reDrawRatio).toInt() 
        if ((x in 0..reDrawX) && (y in 0..reDrawY)) {
   
     
            return "Left_Top" 
        } 
        if ((x in 0..reDrawX) && (y in (aviewHeight - reDrawY)..aviewHeight)) {
   
     
            return "Left_Bottom" 
        } 
        if ((x in (aviewWidth - reDrawX)..aviewWidth) && (y in 0..reDrawY)) {
   
     
            return "Right_Top" 
        } 
        if ((x in (aviewWidth - reDrawX)..aviewWidth) && (y in (aviewHeight - reDrawY)..aviewHeight)) {
   
     
            return "Right_Bottom" 
        } 
        return "Center" 
    } 
 
    /** 
     * 处理拖动事件 
     */ 
    fun contactDrag(v: View, event: MotionEvent, action: Int) {
   
     
        if (action == MotionEvent.ACTION_MOVE) {
   
     
            val dx: Int = event.rawX.toInt() - downRawX 
            val dy: Int = event.rawY.toInt() - downRawY 
            Log.i( 
                "Move3==", 
                "rawX==" + event.rawX.toInt() + "rawY==" + event.rawY.toInt() + "dx==" + dx + "dy==" + dy 
            ) 
            when (direction) {
   
     
                "Center" -> {
   
     
                    center(event.x.toInt(), event.y.toInt(), v) 
                } 
                "Left_Top" -> {
   
    //左上角 
                    left_Top(dx, dy, v) 
                } 
                "Left_Bottom" -> {
   
    //左下角 
                    left_Bottom(dx, dy, v) 
                } 
                "Right_Top" -> {
   
    //右上角 
                    right_Top(dx, dy, v) 
                } 
                "Right_Bottom" -> {
   
    //右下角 
                    right_Bottom(dx, dy, v) 
                } 
            } 
            downRawX = event.rawX.toInt() 
            downRawY = event.rawY.toInt() 
            Log.i("Move3==", "(contactDrag)downRawX==" + downRawX + "downRawY==" + downRawY) 
            if (direction != "Center" ) {
   
     
                v.layout(originalLeft, originalTop, originalRight, originalBottom) 
                //保存位置数据1 
               /* ParamsUtils.listLocation[index].top=originalTop 
                ParamsUtils.listLocation[index].left=originalLeft*/ 
                //保存位置数据2 
                val params: FrameLayout.LayoutParams = layoutParams as FrameLayout.LayoutParams 
                params.leftMargin = originalLeft 
                params.topMargin = originalTop 
                layoutParams = params 
            } 
 
 
        } 
 
 
    } 
 
    /** 
     * 拖动 
     */ 
    fun center(x: Int, y: Int, v: View) {
   
     
        //计算移动的距离 
        var moveX: Int = x - downX 
        var moveY: Int = y - downY 
        if (isMove) {
   
     
            var l = v.left + moveX 
            var r = l + aviewWidth 
            var t = v.top + moveY 
            var b = t + aviewHeight 
            //划出左边边界 
            if (l < 0) {
   
     
                l = 0 
                r = l + aviewWidth 
            } 
            //划出右边边界 
            if (r > mFatherViewWidth) {
   
     
                r = mFatherViewWidth 
                l = r - aviewWidth 
            } 
            //划出上边边界 
            if (t < 0) {
   
     
                t = 0 
                b = t + aviewHeight 
            } 
            //划出下边边界 
            if (b > mFatherViewHeight) {
   
     
                b = mFatherViewHeight 
                t = b - aviewHeight 
            } 
            v.layout(l, t, r, b) 
            Log.i( 
                "Move==", 
                "neft==" + l + "ntop==" + t + "nright==" + r + "nbottom==" + b 
            ) 
           /* //保存位置数据1 
            ParamsUtils.listLocation[index].top=originalTop 
            ParamsUtils.listLocation[index].left=originalLeft*/ 
            //保存位置数据2 
            val params: FrameLayout.LayoutParams = layoutParams as FrameLayout.LayoutParams 
            params.leftMargin = left 
            params.topMargin = top 
            layoutParams = params 
        } 
    } 
 
    fun left_Top(moveX: Int, moveY: Int, v: View) {
   
     
        //最小区域限制 
        if (originalBottom - (originalTop + moveY) > 100) {
   
     
            originalTop += moveY 
        } 
        if (originalRight - (originalLeft + moveX) > 100) {
   
     
            originalLeft += moveX 
        } 
    } 
 
    fun left_Bottom(moveX: Int, moveY: Int, v: View) {
   
     
        if ((originalBottom+moveY) - originalTop  > 100) {
   
     
            originalBottom += moveY 
        } 
 
        if (originalRight - (originalLeft + moveX) > 100) {
   
     
            originalLeft += moveX 
        } 
        limitMastArea() 
    } 
 
    fun right_Top(moveX: Int, moveY: Int, v: View) {
   
     
        if (originalBottom - (originalTop + moveY) > 100) {
   
     
            originalTop += moveY 
        } 
        if ((originalRight + moveX) - originalLeft > 100) {
   
     
            originalRight += moveX 
        } 
        limitMastArea() 
    } 
 
    fun right_Bottom(moveX: Int, moveY: Int, v: View) {
   
     
        if ((originalBottom+moveY) - originalTop  > 100) {
   
     
            originalBottom += moveY 
        } 
        if ((originalRight + moveX) - originalLeft > 100) {
   
     
            originalRight += moveX 
        } 
        limitMastArea() 
        Log.i("Move3==", "originalBottom==" + originalBottom + "originalRight==" + originalRight) 
    } 
 
    /** 
     *  限制最大区域 
     */ 
    fun limitMastArea() {
   
     
        if (originalTop < 0) {
   
     
            originalTop = 0 
        } 
        if (originalLeft < 0) {
   
     
            originalLeft = 0 
        } 
        if (originalBottom > mFatherViewHeight) {
   
     
            originalBottom = mFatherViewHeight 
        } 
        if (originalRight > mFatherViewWidth) {
   
     
            originalRight = mFatherViewWidth 
        } 
    } 
 
 
    /** 
     * 选中 
     */ 
    fun Choose() {
   
     
        //重置选中paint 
        mPaintK!!.reset() 
        //把画笔颜色设置为透明,重绘 
        mPaintK!!.isAntiAlias = antiAlias 
        // 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE 
        mPaintK!!.style = Paint.Style.STROKE 
        // 设置画笔粗细 
        mPaintK!!.strokeWidth = 2 * mArcWidth 
        mPaintK!!.color = resources.getColor(R.color.tv_color_00) 
        mPaintK!!.textAlign = Paint.Align.CENTER 
        //重置选中paint 
        mPaintAngel!!.reset() 
        mPaintAngel!!.isAntiAlias = antiAlias 
        // 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE 
        mPaintAngel!!.style = Paint.Style.STROKE 
        // 设置画笔粗细 
        mPaintAngel!!.strokeWidth = 4 * mArcWidth 
        mPaintAngel!!.color = resources.getColor(R.color.tv_color_00) 
        mPaintAngel!!.textAlign = Paint.Align.CENTER 
        invalidate() 
        //设置选中标记 
       /* ParamsUtils.listLocation.forEach { 
            it.check=0 
        } 
        ParamsUtils.listLocation[index].check=1 
        EventBus.getDefault().post(PayEven("改变选中状态"))*/ 
    } 
    // 设置回调接口 
    fun setRudderListener(rockerListener: RudderListener?) {
   
     
        listener = rockerListener 
    } 
 
    // 回调接口 
    interface RudderListener {
   
     
        fun onSteeringWheelChanged( 
            cross: Float, 
            longitudinal: Float 
        ) 
 
        fun isChoose(isChoose: Boolean) 
    } 
 
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
   
     
        super.onMeasure(widthMeasureSpec, heightMeasureSpec) 
        setMeasuredDimension( 
            aviewWidth, 
            aviewHeight 
        ) 
    } 
 
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
   
     
        super.onSizeChanged(w, h, oldw, oldh) 
        aviewWidth = w 
        aviewHeight = h 
    } 
 
 
 
} 

类代码:

var recnum =0 
    /** 
     * 添加矩形 
     */ 
    fun addRectangle() {
   
     
        if (recnum < 3) {
   
     
            var RectangleView = 
                MyRectangleView(this, waiWidth, waiHeight, recnum) 
            RectangleView.id = recnum 
            li_wai.addView(RectangleView) 
            recnum++ 
        } 
    } 

遇到问题
(一)在实现重绘的时候,走了点弯路,我一开始朝着

/** 
 *在view 中调用requestLayout();view会重新计算大小, 
 *也就是会重新执行onMeasure 方法。在onMeasure中调用 
 *setMeasuredDimension(tempWidth, tempHeight); 就会重新设定view的大小 
**/ 
requestLayout(); 

的方向走,发现通过该方法的确能修改View背景画布的大小,但是放在我的代码里面,重绘的时候并没有改变画布上绘制矩形的尺寸,我又在requestLayout后重新调用了onDraw()里面的方法重新绘制矩形,运行出来发现异常卡顿,之后又换了思路使用v.layout(l, t, r, b)+ invalidate()的方法,发现异常顺利。 invalidate()调用后,会重新走onDraw()的方法。
(二)这个功能是同事项目里面的一个小功能,我试图自己来试着实现,这个功能里面还有一个我没实现的就是,这个矩形可以点击按钮动态添加,点击矩形选中变色,点击页面空白处矩形取消选中,页面多个矩形时候,只能选中一个,因为自定义VIew正在学习中,这个功能我暂时还没想到简单的实现方法,我暂时想到的方法就是:添加tag的方法,将矩形位置数据保存起来,每次点击后发送通知,父布局收到通知后removeAllViews(),然后再根据保存的位置数据重新addView()并根据保存的 tag数据设置是否变色,不知有思路的大佬可否指点一二(笑脸)


评论关闭
IT虾米网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

自定义View学习(三)自定义可拖动的RecyclerView