IT虾米网

自定义View学习一拖动

luoye 2022年05月05日 手机开发 201 0

简单可拖动自定义View

可移动光标

需求:
1、光标样式
2、可拖动
3、拖动时颜色改变
4、设置拖动边界
在这里插入图片描述
光标实现:

/** 
 * 光标 
 */ 
class CurSorView(context: Context?, fatherViewWidth: Int, fatherViewHeight: Int, index: Int) : 
    View(context) {
   
     
    //是否开启抗锯齿 
    private val antiAlias = true 
    private val mArcWidth = 1f 
 
    //    var indexNum = 1//添加下标 
    var indexNum = index//添加下标 
    var mContext = context; 
 
    //中心点 
    var floatX = 0f 
    var floatY = 0f 
    var mPaintK: Paint? = null//默认边框 
    var mPaintChoose: Paint? = null//选中边框 
    var mPaintC: Paint? = null//填充 
    var mPaintT: Paint? = null//文字 
    var mPaintB: Paint? = null//背景 
    var rect: RectF? = null//背景 
 
    //父布局范围 
    var mFatherViewWidth = fatherViewWidth 
    var mFatherViewHeight = fatherViewHeight 
 
 
    //圆半径 
    var floatCircleRadius = 5f 
 
    //圆心坐标,半径 
    private var mCenterPoint: Point? = null 
 
    //矩形宽高 
    var floatRecHeight = 5 * floatCircleRadius 
    var floatRecWidth = floatCircleRadius - 2 
    var distance = 0 
 
    //view宽高 
    var mDefaultSize = 0 
    var viewWidth = 0 
    var viewHeight = 0 
 
    var mCanvas: Canvas? = null 
 
    //onTouch使用 
    var nleft = 0 
    var ntop = 0 
    var nright = 0 
    var nbottom = 0 
 
 
    init {
   
     
        mDefaultSize = 60 
        initData() 
        initPaint() 
    } 
 
    /** 
     * 计算各个距离 
     */ 
    fun initData() {
   
     
        //根据半径获取原点距离其他点的垂直距离 
        //勾股定理 
        distance = 
            Math.sqrt((floatCircleRadius * floatCircleRadius - (floatRecWidth / 2) * (floatRecWidth / 2)).toDouble()) 
                .toInt() 
        mCenterPoint = Point() 
    } 
 
    @SuppressLint("ResourceType") 
    fun initPaint() {
   
     
        /** 
         * 默认边框 
         */ 
        mPaintK = Paint() 
        mPaintK!!.isAntiAlias = antiAlias 
        // 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE 
        mPaintK!!.style = Paint.Style.STROKE 
        // 设置画笔粗细 
        mPaintK!!.strokeWidth = 3 * mArcWidth 
        mPaintK!!.color = Color.WHITE 
        mPaintK!!.textAlign = Paint.Align.CENTER 
        /** 
         * 选中边框 
         */ 
        mPaintChoose = Paint() 
        mPaintChoose!!.isAntiAlias = antiAlias 
        // 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE 
        mPaintChoose!!.style = Paint.Style.STROKE 
        // 设置画笔粗细 
        mPaintChoose!!.strokeWidth = 3 * mArcWidth 
        mPaintChoose!!.color = Color.TRANSPARENT 
        mPaintChoose!!.textAlign = Paint.Align.CENTER 
        /** 
         * 填充 
         */ 
        mPaintC = Paint() 
        mPaintC!!.isAntiAlias = antiAlias 
        // 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE 
        mPaintC!!.style = Paint.Style.FILL 
        // 设置画笔粗细 
        mPaintC!!.strokeWidth = mArcWidth 
        mPaintC!!.color = Color.BLACK 
        mPaintC!!.textAlign = Paint.Align.CENTER 
        /** 
         * 文字 
         */ 
        mPaintT = Paint() 
        mPaintT!!.isAntiAlias = antiAlias 
        // 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE 
        mPaintT!!.style = Paint.Style.STROKE 
        // 设置画笔粗细 
        mPaintT!!.strokeWidth = mArcWidth 
        mPaintT!!.color = Color.BLACK 
        mPaintT!!.textSize = 15f 
        mPaintT!!.textAlign = Paint.Align.CENTER 
    } 
 
    override fun onDraw(canvas: Canvas?) {
   
     
        super.onDraw(canvas) 
        mCanvas = canvas 
        drawKuang(mCanvas)//绘制默认边框 
        drawCenter(mCanvas)//绘制填充 
        drawChooseKuang(mCanvas)//绘制选中边框 
        drawText(mCanvas) 
    } 
 
    /** 
     * 绘制边框 
     */ 
    fun drawKuang(canvas: Canvas?) {
   
     
        //画中心圆 
        canvas!!.drawCircle(floatX, floatY, floatCircleRadius, mPaintK!!) 
        //绘制左边矩形 
        val pathG = Path() 
        pathG.moveTo(floatX - distance, floatY - floatRecWidth / 2)//起点  暂定右上角为起点 
        pathG.lineTo(floatX - distance - floatRecHeight, floatY - floatRecWidth / 2)//左上角 
        pathG.lineTo(floatX - distance - floatRecHeight, floatY + floatRecWidth / 2)//左下角 
        pathG.lineTo(floatX - distance, floatY + floatRecWidth / 2)//右下角 
//        pathG.close() // 使这些点构成封闭的多边形 
        canvas.drawPath(pathG, mPaintK!!) 
        //绘制上边矩形 
        pathG.reset() 
        pathG.moveTo(floatX - floatRecWidth / 2, floatY - distance)//起点  暂定左下角为起点 
        pathG.lineTo(floatX - floatRecWidth / 2, floatY - distance - floatRecHeight)//左上角 
        pathG.lineTo(floatX + floatRecWidth / 2, floatY - distance - floatRecHeight)//右上角 
        pathG.lineTo(floatX + floatRecWidth / 2, floatY - distance)//右下角 
        canvas.drawPath(pathG, mPaintK!!) 
        //绘制右边矩形 
        pathG.reset() 
        pathG.moveTo(floatX + distance, floatY - floatRecWidth / 2)//起点  暂定左上角为起点 
        pathG.lineTo(floatX + distance + floatRecHeight, floatY - floatRecWidth / 2)//右上角 
        pathG.lineTo(floatX + distance + floatRecHeight, floatY + floatRecWidth / 2)//右下角 
        pathG.lineTo(floatX + distance, floatY + floatRecWidth / 2)//左下角 
        canvas.drawPath(pathG, mPaintK!!) 
        //绘制下边矩形 
        pathG.reset() 
        pathG.moveTo(floatX - floatRecWidth / 2, floatY + distance)//起点  暂定左上角为起点 
        pathG.lineTo(floatX - floatRecWidth / 2, floatY + distance + floatRecHeight)//左下角 
        pathG.lineTo(floatX + floatRecWidth / 2, floatY + distance + floatRecHeight)//右下角 
        pathG.lineTo(floatX + floatRecWidth / 2, floatY + distance)//右上角 
        canvas.drawPath(pathG, mPaintK!!) 
    } 
 
    /** 
     * 绘制选中边框 
     */ 
    fun drawChooseKuang(canvas: Canvas?) {
   
     
        //画中心圆 
        canvas!!.drawCircle(floatX, floatY, floatCircleRadius, mPaintChoose!!) 
        //绘制左边矩形 
        val pathG = Path() 
        pathG.moveTo(floatX - distance, floatY - floatRecWidth / 2)//起点  暂定右上角为起点 
        pathG.lineTo(floatX - distance - floatRecHeight, floatY - floatRecWidth / 2)//左上角 
        pathG.lineTo(floatX - distance - floatRecHeight, floatY + floatRecWidth / 2)//左下角 
        pathG.lineTo(floatX - distance, floatY + floatRecWidth / 2)//右下角 
//        pathG.close() // 使这些点构成封闭的多边形 
        canvas.drawPath(pathG, mPaintChoose!!) 
        //绘制上边矩形 
        pathG.reset() 
        pathG.moveTo(floatX - floatRecWidth / 2, floatY - distance)//起点  暂定左下角为起点 
        pathG.lineTo(floatX - floatRecWidth / 2, floatY - distance - floatRecHeight)//左上角 
        pathG.lineTo(floatX + floatRecWidth / 2, floatY - distance - floatRecHeight)//右上角 
        pathG.lineTo(floatX + floatRecWidth / 2, floatY - distance)//右下角 
        canvas.drawPath(pathG, mPaintChoose!!) 
        //绘制右边矩形 
        pathG.reset() 
        pathG.moveTo(floatX + distance, floatY - floatRecWidth / 2)//起点  暂定左上角为起点 
        pathG.lineTo(floatX + distance + floatRecHeight, floatY - floatRecWidth / 2)//右上角 
        pathG.lineTo(floatX + distance + floatRecHeight, floatY + floatRecWidth / 2)//右下角 
        pathG.lineTo(floatX + distance, floatY + floatRecWidth / 2)//左下角 
        canvas.drawPath(pathG, mPaintChoose!!) 
        //绘制下边矩形 
        pathG.reset() 
        pathG.moveTo(floatX - floatRecWidth / 2, floatY + distance)//起点  暂定左上角为起点 
        pathG.lineTo(floatX - floatRecWidth / 2, floatY + distance + floatRecHeight)//左下角 
        pathG.lineTo(floatX + floatRecWidth / 2, floatY + distance + floatRecHeight)//右下角 
        pathG.lineTo(floatX + floatRecWidth / 2, floatY + distance)//右上角 
        canvas.drawPath(pathG, mPaintChoose!!) 
    } 
 
    /** 
     * 取消选中 
     */ 
    fun cancelChoose() {
   
     
        //重置选中paint 
        mPaintChoose!!.reset() 
        //把画笔颜色设置为透明,重绘 
        mPaintChoose!!.color = Color.TRANSPARENT 
        invalidate() 
    } 
 
    /** 
     * 选中 
     */ 
    fun Choose() {
   
     
        //重置选中paint 
        mPaintChoose!!.reset() 
        //把画笔颜色设置为透明,重绘 
        mPaintChoose!!.color = resources.getColor(R.color.tv_color_00) 
        invalidate() 
    } 
 
    /** 
     * 绘制填充 
     */ 
    fun drawCenter(canvas: Canvas?) {
   
     
        //画中心圆 
        canvas!!.drawCircle(floatX, floatY, floatCircleRadius - mArcWidth / 2, mPaintC!!) 
        //上部矩形 
        canvas.drawRect( 
            floatX - floatRecWidth / 2 + mArcWidth / 2, 
            floatY - distance - floatRecHeight + mArcWidth / 2, 
            floatX + floatRecWidth / 2 - mArcWidth / 2, 
            floatY - distance, 
            mPaintC!! 
        ) 
        //下部矩形 
        canvas.drawRect( 
            floatX - floatRecWidth / 2 + mArcWidth / 2, 
            floatY + distance, 
            floatX + floatRecWidth / 2 - mArcWidth / 2, 
            floatY + distance + floatRecHeight - mArcWidth / 2, 
            mPaintC!! 
        ) 
        //左边矩形 
        canvas.drawRect( 
            floatX - distance - floatRecHeight + mArcWidth / 2, 
            floatY - floatRecWidth / 2 + mArcWidth / 2, 
            floatX - distance, 
            floatY + floatRecWidth / 2 - mArcWidth / 2, 
            mPaintC!! 
        ) 
        //右边矩形 
        canvas.drawRect( 
            floatX + distance, 
            floatY - floatRecWidth / 2 + mArcWidth / 2, 
            floatX + distance + floatRecHeight - mArcWidth / 2, 
            floatY + floatRecWidth / 2 - mArcWidth / 2, 
            mPaintC!! 
        ) 
    } 
 
    /** 
     * 绘制下标 
     */ 
    fun drawText(canvas: Canvas?) {
   
     
        canvas!!.drawText( 
            indexNum.toString(), 
            floatX + floatRecHeight / 2, 
            floatY + floatRecHeight, 
            mPaintT!! 
        ) 
    } 
 
    /** 
     * rawX  rawY 触摸点距离屏幕的距离 
     *   x      y 触摸点距离view边界的距离 
     */ 
    override fun onTouchEvent(event: MotionEvent?): Boolean {
   
     
        var downX = 0 
        var downY = 0 
 
        when (event!!.action) {
   
     
            MotionEvent.ACTION_DOWN -> {
   
     
                downX = event!!.x.toInt() 
                downY = event.y.toInt() 
                Choose() 
            } 
            MotionEvent.ACTION_MOVE -> {
   
     
                //计算移动的距离 
                var moveX: Int = event!!.x.toInt() - downX 
                var moveY: Int = event!!.y.toInt() - downY 
                var l = left + moveX 
                var r = l + viewWidth 
                var t = top + moveY 
                var b = t + viewHeight 
                //划出左边边界 
                if (l < 0) {
   
     
                    l = 0 
                    r = l + viewWidth 
                } 
                //划出右边边界 
                if (r > mFatherViewWidth) {
   
     
                    r = mFatherViewWidth 
                    l = r - viewWidth 
                } 
                //划出上边边界 
                if (t < 0) {
   
     
                    t = 0 
                    b = t + viewHeight 
                } 
                //划出下边边界 
                if (b > mFatherViewHeight) {
   
     
                    b = mFatherViewHeight 
                    t = b - viewHeight 
                } 
                layout(l, t, r, b) 
                Log.i( 
                    "Move==", 
                    "neft==" + l + "ntop==" + t + "nright==" + r + "nbottom==" + b 
                ) 
                val params: FrameLayout.LayoutParams = layoutParams as FrameLayout.LayoutParams 
                params.leftMargin=left 
                params.topMargin=top 
                layoutParams=params 
            } 
            MotionEvent.ACTION_UP -> {
   
     
                cancelChoose() 
            } 
        } 
        return true 
    } 
    /** 
     * 移动过快时候会出现 距离位置监听不到的情况  例如限制距离上部50  但是移动过快会在top=-6时候才监听出来  而这时候 
     */ 
 
    /** 
     * 以下两个方法设置View居中画布显示 
     * 执行顺序(1)onMeasure(2)onSizeChanged 
     */ 
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
   
     
        super.onSizeChanged(w, h, oldw, oldh) 
        //获取圆的相关参数    w即onMeasure中设置的  设置View居中画布显示 
        floatX = (w / 2).toFloat() 
        floatY = (h / 2).toFloat() 
        viewWidth = measuredWidth 
        viewHeight = measuredHeight 
    } 
 
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
   
     
        super.onMeasure(widthMeasureSpec, heightMeasureSpec) 
        setMeasuredDimension( 
            MiscUtil.measure(widthMeasureSpec, mDefaultSize), 
            MiscUtil.measure(heightMeasureSpec, mDefaultSize) 
        ) 
    } 
 
 
} 

添加光标:
xml代码:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:orientation="vertical" 
    tools:context=".mycustomview.LinePointActivity"> 
    <!--    android:layout_marginLeft="30dp" 
            android:layout_marginRight="30dp" 
            android:layout_marginTop="100dp" 
            android:layout_marginBottom="100dp"--> 
    <LinearLayout 
        android:layout_width="match_parent" 
        android:layout_height="50dp" 
        android:gravity="center" 
        android:orientation="horizontal"> 
 
        <TextView 
            android:id="@+id/tv_addcursor" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:background="@color/colorPrimary" 
            android:gravity="center" 
            android:padding="10dp" 
            android:text="添加光标" 
            android:textColor="@android:color/white" 
            android:textSize="17sp" /> 
 
 
    </LinearLayout> 
 
    <FrameLayout 
        android:id="@+id/li_wai" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" 
        android:background="@color/colorAccent" 
        android:orientation="vertical"></FrameLayout> 
 
 
</LinearLayout> 
  

类代码:

 
  //view中心坐标 
    var strX = arrayOf("193", "455", "234", "511", "545") 
    var strY = arrayOf("288", "432", "650", "176", "782") 
     //父布局 
    var waiWidth = 0 
    var waiHeight = 0 
    var num =0 
     fun init() {
   
     
        //父布局范围--即可拖动范围 
        li_wai.post(Runnable {
   
     
            waiWidth=li_wai.width 
            waiHeight=li_wai.height 
            Log.w("tv_width", "" + li_wai.getWidth()) 
            Log.w("tv_height", "" + li_wai.getHeight()) 
        }) 
    } 
 
 /** 
     * 添加光标 
     */ 
    fun addCurSor() {
   
     
//        for (i in strX.indices) {
   
     
        if (num < strX.size) {
   
     
            var cursorView = 
                CurSorView(this, waiWidth, waiHeight, num + 1) 
            var cLayoutParams = LinearLayout.LayoutParams( 
                LinearLayout.LayoutParams.WRAP_CONTENT, 
                LinearLayout.LayoutParams.WRAP_CONTENT 
            ) 
            cLayoutParams.setMargins( 
                strX[num].toInt() - 75, 
                strY[num].toInt() - 75, 
                0, 
                0 
            )//margin是相对于边的  减去view/2  即相当于中心的位置 
            cursorView.layoutParams = cLayoutParams 
//        cursorView.setBackgroundColor(resources.getColor(R.color.orange)) 
            li_wai.addView(cursorView) 
            num++ 
        } 
//        } 
    } 

== 遇到问题==
在实现过程中,由于设置的时点击一次按钮添加一个View,再次点击添加时候,发现之前添加并且移动过位置的view,会被重置为默认位置,即恢复到初始位置,经过百度发现,使用layout和offsetLeftAndRight方法进行重绘时候,并没有把最新的位置信息保存到LayoutParams中,所以才会出现上述情况,解决方案如下:(经测可用)

 /** 
 * 由于使用addview()添加控件后,移动控件位置,位置信息并没有更新到layoutParams中 , 
 * 所以会出现,再次addView时候,原有的已经移动过的位置会回复到默认值 
 * 需要重新配置下layoutParams信息,将最新的位置信息更新至layoutParams中 
 */ 
 val params: FrameLayout.LayoutParams = layoutParams as FrameLayout.LayoutParams 
 params.leftMargin=left 
 params.topMargin=top 
 layoutParams=params 
 

评论关闭
IT虾米网

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

自定义View学习(二)拖动+重绘