android自定义view【控件篇】

kotlin语法总结

自定义属性与自定义style

假如我们想要自定义textview 的属性
先在res/value 下创建文件 attrs.xml




    

        
        
        

        
            
            
            
        

    


然后在xml根布局中添加命名空间 这里的MyTextView可以定义为任何名字xmlns:MyTextView="http://schemas.android.com/apk/res-auto"
接着就可以在具体的自定义view标签中使用 MyTextView: 属性名 = 具体的值







接下来在代码中 使用TypedArray获取属性的值

class View12: TextView {


    constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){

        //  传入attr.xml 文件中定义的Styleable名称
        var typedArray = context?.obtainStyledAttributes(attrs , R.styleable.MyTextView)

        // 根据属性的不同format  调用TypedArray不同的函数获取对应的值
        var headerHeight = typedArray?.getDimension(R.styleable.MyTextView_headerHeight, -1f);
        var age = typedArray?.getInt(R.styleable.MyTextView_age , -1)

        //获取完毕之后需要释放资源
        typedArray?.recycle()

        setText("headerHeight: $headerHeight  age: $age")

    }

}

效果如图所示


自定义属性.jpg

自定义属性的format类型

转自 https://blog.csdn.net/dj0379/article/details/49662161
1. reference:参考某一资源ID。

(1)属性定义:

        

               

        

(2)属性使用:

         

2. color:颜色值。

(1)属性定义:

        

               

        

(2)属性使用:

        

3. boolean:布尔值。

(1)属性定义:

        

               

        

(2)属性使用:

        

4. dimension:尺寸值。

(1)属性定义:

        

               

        

(2)属性使用:

        

5. float:浮点值。

(1)属性定义:

        

               
               

        

(2)属性使用:

        

6. integer:整型值。

(1)属性定义:

        

               
               
               
               
               
               

        

(2)属性使用:

        

7. string:字符串。

(1)属性定义:

        
               
        

(2)属性使用:

        

8. fraction:百分数。

(1)属性定义:

        
               
               
               
               
               
               
        

(2)属性使用:

        

android:interpolator = "@anim/动画ID"

               android:fromDegrees = "0" 

android:toDegrees = "360"

               android:pivotX = "200%"

               android:pivotY = "300%" 

android:duration = "5000"

               android:repeatMode = "restart"

               android:repeatCount = "infinite"

               />

9. enum:枚举值。

(1)属性定义:

        
               
                      
                      
                           

        

(2)属性使用:

        
        

10. flag:位或运算。

 (1)属性定义:

         
                
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                          

         

 (2)属性使用:

        
               
                      
                      
               
         

 注意:

 属性定义时可以指定多种类型值。

(1)属性定义:

        

               

        

(2)属性使用:

         

测量与布局

ViewGroup绘制流程

onMeasure 测量当前控件的大小
onLayuot 使用layout函数对所有子控件进行布局
onDraw 根据布局的位置绘图

onMeasure函数与MeasureSpec

onMeasure 函数测量完成之后 要通过void setMeasuredDimension(int measuredWidth, int measuredHeight) 函数设置给系统 给onLayuot函数提供数值

MeasureSpec的组成
measuredWidth 和 measuredHeight 都是int 类型 在内存中都是32位
前两位代表mode 后30位代表数值
有三种模式
未指定 UNSPECIFIED = 0 << MODE_SHIFT; 子元素可以得到任意想要的大小
确切的值 EXACTLY = 1 << MODE_SHIFT; 子元素有确定的大小
最多 AT_MOST = 2 << MODE_SHIFT; 子元素可以得到最大的大小

其中MODE_SHIFT = 30
也就是三种模式的二进制值分别是 0 1 2 向左移30位 即
00 0000000000 0000000000 0000000000
01 0000000000 0000000000 0000000000
10 0000000000 0000000000 0000000000

模式提取
MeasureSpec 提供了函数获取measuredWidth或者measuredHeight 的模式

 public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
二进制值为 
11 0000000000 0000000000 0000000000

数值提取
MeasureSpec 也提供了函数

 public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
~MODE_MASK  二进制值为
00 1111111111 1111111111 1111111111

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
模式与数值主要用到了 与 非 运算

xml 中定义的大小 对应的模式

xml定义 对应的模式 描述
wrap_content AT_MOST 最多得到父控件的大小
match_parent EXACTLY 得到的是父控件已经确定的大小
具体的值 EXACTLY 指定的大小

UNSPECIFIED 基本用不到

onLayout 函数

void onLayout(boolean changed, int left, int top, int right, int bottom)
在ViewGroup中这是一个抽象函数 说明凡事派生自ViewGoup 的类都必须自己去实现这个函数
像 LinearLayout RelativeLayout 等布局都重写了这个函数 然后在按照自己的规则对子视图进行布局

示例 自定义LinearLayout

自定义 纵向显示的LinearLayout

class View12LinearLayout : ViewGroup {

    constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){

    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        var height = 0
        var width = 0
        for (index in 0 until childCount){
            // 测量子控件
            var child = getChildAt(index)
            measureChild(child , widthMeasureSpec , heightMeasureSpec)
            var childHeight = child.measuredHeight
            var childWidth = child.measuredWidth
            // 高度累加  宽度取最大值
            height += childHeight
            width = Math.max(childWidth , width)
        }

        // 告诉系统计算完的值
        setMeasuredDimension( if(widthMode == MeasureSpec.EXACTLY) widthSize else width ,
            if(heightMode == MeasureSpec.EXACTLY) heightSize else height)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        var top = 0
        var count = childCount
        for (index in 0 until count){

            var child = getChildAt(index)
            var childheight = child.measuredHeight
            var childwidth = child.measuredWidth

            // 遍历每一个ziview   然后设置其布局位置
            // 因为是纵向排列 x起点是0  y起点累加子view的高度
            child.layout(0 ,top ,childwidth , top+childheight)
            top += childheight
        }
    }


}

xml布局




    

    

    


获取子view控件的 margin

现在我们把第二个view 设置margin值 运行起来之后发现并没有效果
这是因为测量和布局都是我们自己实现的没有根据margin布局

 

接下来我们需要重写三个函数

 override fun generateLayoutParams(p: LayoutParams?): LayoutParams {
        return MarginLayoutParams(p)
    }

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
        return MarginLayoutParams(context , attrs)
    }

    override fun generateDefaultLayoutParams(): LayoutParams {
        return MarginLayoutParams(LayoutParams.MATCH_PARENT , LayoutParams.MATCH_PARENT)
    }

然后修改 onMeasure 和 onLayout 的部分代码

onMeasure 修改 
    for (index in 0 until childCount){
            // 测量子控件
            var child = getChildAt(index)

//            获取margin
            var lp = child.layoutParams as MarginLayoutParams

            measureChild(child , widthMeasureSpec , heightMeasureSpec)

            // 计算高度加上 margintop  和 marginbottom
            var childHeight = child.measuredHeight + lp.topMargin + lp.bottomMargin
            var childWidth = child.measuredWidth + lp.leftMargin + lp.rightMargin
            // 高度累加  宽度取最大值
            height += childHeight
            width = Math.max(childWidth , width)
        }


onLayout 修改
 for (index in 0 until count){

            var child = getChildAt(index)

            //            获取margin
            var lp = child.layoutParams as MarginLayoutParams

            var childheight = child.measuredHeight
            var childwidth = child.measuredWidth + lp.leftMargin + lp.rightMargin

            // 遍历每一个ziview   然后设置其布局位置
            // 因为是纵向排列 x起点是0  y起点累加子view的高度
            // 布局位置 以及子view高度  根据margin计算
            child.layout(0 ,top + lp.topMargin  ,childwidth , top + lp.topMargin +childheight )
            top += childheight  + lp.topMargin + lp.bottomMargin
        }

margin布局.jpg

为什么要把 child.layoutParams 强转成 MarginLayoutParams
首先container初始化子控件时, 会调用generateLayoutParams 函数来为子控件生成对应的布局属性 但默认只生成 layout_width 和 layout_height , 即正常情况下调用generateLayoutParams函数生成的layoutparams 实例是不能获取到 margin的

如果我们还需要与margin相关的参数就只能重写generateLayoutParams函数

Viewgroup源码

public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

  public LayoutParams(Context c, AttributeSet attrs) {
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
        }

 protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            width = a.getLayoutDimension(widthAttr, "layout_width");
            height = a.getLayoutDimension(heightAttr, "layout_height");
        }

可以看出generateLayoutParams生成的layoutparams最终是由setBaseAttributes决定的
而setBaseAttributes函数值获取了 layout_width 和 layout_height

MarginLayoutParams 源码

public MarginLayoutParams(Context c, AttributeSet attrs) {
            super();

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_MarginLayout_layout_width,
                    R.styleable.ViewGroup_MarginLayout_layout_height);

            int margin = a.getDimensionPixelSize(
                    com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
            if (margin >= 0) {
                leftMargin = margin;
                topMargin = margin;
                rightMargin= margin;
                bottomMargin = margin;
            } else {
                int horizontalMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
                int verticalMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);

                if (horizontalMargin >= 0) {
                    leftMargin = horizontalMargin;
                    rightMargin = horizontalMargin;
                } else {
                    leftMargin = a.getDimensionPixelSize(
                            R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
                            UNDEFINED_MARGIN);
                    if (leftMargin == UNDEFINED_MARGIN) {
                        mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
                        leftMargin = DEFAULT_MARGIN_RESOLVED;
                    }
                    rightMargin = a.getDimensionPixelSize(
                            R.styleable.ViewGroup_MarginLayout_layout_marginRight,
                            UNDEFINED_MARGIN);
                    if (rightMargin == UNDEFINED_MARGIN) {
                        mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
                        rightMargin = DEFAULT_MARGIN_RESOLVED;
                    }
                }

                startMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginStart,
                        DEFAULT_MARGIN_RELATIVE);
                endMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
                        DEFAULT_MARGIN_RELATIVE);

                if (verticalMargin >= 0) {
                    topMargin = verticalMargin;
                    bottomMargin = verticalMargin;
                } else {
                    topMargin = a.getDimensionPixelSize(
                            R.styleable.ViewGroup_MarginLayout_layout_marginTop,
                            DEFAULT_MARGIN_RESOLVED);
                    bottomMargin = a.getDimensionPixelSize(
                            R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
                            DEFAULT_MARGIN_RESOLVED);
                }

                if (isMarginRelative()) {
                   mMarginFlags |= NEED_RESOLUTION_MASK;
                }
            }

            final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
            final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
            if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
                mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
            }

            // Layout direction is LTR by default
            mMarginFlags |= LAYOUT_DIRECTION_LTR;

            a.recycle();
        }

可以看见 MarginLayoutParams获取了margin 属性 并且优先获取margin 其次才是 marginleft marginright等属性

实现FlowLayout 布局

给子view设置通用的style style.xml文件下添加代码

 

布局文件代码






    


    

    

    

    

    

    

    

    





自定义view 代码

package com.bhb.cutomview.view

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup

/**
 *  create by BHB on 7/10/21
 *    自定义 FlowLayout 布局
 *
 */

//在xml标签上添加margin属性 发现运行之后没有效果  这是因为测量和布局都是我们自己实现的没有根据margin布局

class View12FlowLayout : ViewGroup {

    constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){

    }

    override fun generateLayoutParams(p: LayoutParams?): LayoutParams {
        return MarginLayoutParams(p)
    }

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
        return MarginLayoutParams(context , attrs)
    }

    override fun generateDefaultLayoutParams(): LayoutParams {
        return MarginLayoutParams(LayoutParams.MATCH_PARENT , LayoutParams.MATCH_PARENT)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        var lineWidth = 0   // 当前行的宽度
        var lineHeight = 0  // 当前行的高度
        var width = 0    // 控件总宽度
        var height = 0  //控件总高度

        for(i in 0 until childCount){
            var child = getChildAt(i)
            // 先测量子view 才能获取宽度 高度
            measureChild(child , widthMeasureSpec , heightMeasureSpec)
            var lp = child.layoutParams as MarginLayoutParams
            var childWidth = child.measuredWidth + lp.leftMargin + lp.rightMargin
            var childHeight = child.measuredHeight + lp.topMargin + lp.bottomMargin

            // 绘制第一个view 的时候  height  和lineheight 的值就是  子view的高度
            if(i == 0){
                height = childHeight
                lineHeight = childHeight
            }
            if(lineWidth + childWidth > widthSize){
                //需要换行
                width = Math.max(lineWidth , width)
                height+= lineHeight

                //换行之后 当前行的宽高 就是当前子view的宽高
                lineHeight += childHeight
                lineWidth = childWidth
            }else{
                //在当前行继续排列
                lineHeight = Math.max(lineHeight , childHeight)
                lineWidth += childWidth
            }

            setMeasuredDimension( if(widthMode == MeasureSpec.EXACTLY) widthSize else width ,
                if(heightMode == MeasureSpec.EXACTLY) heightSize else height)

        }


    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        var top = 0
        var left = 0
        var lineWith = 0
        var lineHeight = 0
        var count = childCount
        for (index in 0 until count){

            var child = getChildAt(index)

            //            获取margin
            var lp = child.layoutParams as MarginLayoutParams

            var childheight = child.measuredHeight + lp.topMargin + lp.bottomMargin
            var childwidth = child.measuredWidth + lp.leftMargin + lp.rightMargin

            if(childwidth + lineWith > measuredWidth){
                top += lineHeight
                left = 0

                lineHeight  = childheight
                lineWith = childwidth
            }else{
                lineHeight = Math.max(lineHeight , childheight)
                lineWith += childwidth
            }

            //计算childview 的left top right bottom
            var lc = left + lp.leftMargin
            var tc = top + lp.topMargin
            var rc = lc + child.measuredWidth
            var bc = tc + child.measuredHeight

            child.layout(lc, tc , rc , bc )

            //将left 置为下一个子控件的起点
            left += childwidth
        }
    }


}

显示效果


flowlayout.jpg
GestureDetector 手势检测

我们知道 View类有一个 onTouchListener 内部接口 通过重写它的 onTouch函数可以处理一些touch事件
但是这个函数太过简单 如果需要处理一些复杂的手势 使用这个接口就会很麻烦
android 给我们提供了GestureDetector 这个类可以识别很多手势 在识别出手势之后 具体的业务逻辑由开发者自己实现

class MainActivity13 : AppCompatActivity(), View.OnTouchListener{


    lateinit var gestureDetector :GestureDetector
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main13)

        // 设置监听  构造函数有多个
        gestureDetector = GestureDetector(GestureListener())
        gestureDetector.setOnDoubleTapListener(DoubleTapListener())

        // GestureDetector 还有一个监听函数
        gestureDetector = GestureDetector(object : GestureDetector.SimpleOnGestureListener() {
            //这里包含了GestureListener 和  DoubleTapListener 的所有函数
//            需要就进行重写  不需要则不处理

            override fun onDown(e: MotionEvent?): Boolean {
                return super.onDown(e)
            }

            override fun onDoubleTap(e: MotionEvent?): Boolean {
                return super.onDoubleTap(e)
            }

            // 判断用户是左滑 还是 右滑
            // 判断标准  用户向左滑动距离超100像素 且移动速度超过200像素/秒 即认为是左滑  右滑同理
            override fun onFling(
                e1: MotionEvent?,
                e2: MotionEvent?,
                velocityX: Float,
                velocityY: Float
            ): Boolean {
                if(e1 == null || e2 == null){
                    return super.onFling(e1, e2, velocityX, velocityY)
                }
                Log.e("view13" , "velocityX =====  $velocityX")

                if( (e1.x - e2.x )> 100  && Math.abs(Math.abs(velocityX)) > 200 ){
                    Log.e("view13" , "fling left")
                }else if((e2.x - e1.x )> 100  && Math.abs(Math.abs(velocityX)) > 200 ){
                    Log.e("view13" , "fling right")
                }
                return true
            }

        })

        textview.setOnTouchListener(this)
        textview.isFocusable = true
        textview.isClickable = true
        textview.isLongClickable = true

    }

    // 将事件交给GestureDetector 处理
    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        return gestureDetector.onTouchEvent(event)
    }


    class GestureListener : GestureDetector.OnGestureListener{
        override fun onShowPress(e: MotionEvent?) {
           // 按压事件  且按下的时间超过瞬间
            Log.e("view13", "onShowPress")
        }

        override fun onLongPress(e: MotionEvent?) {
            // 长按事件
            Log.e("view13", "onLongPress")
        }

        override fun onSingleTapUp(e: MotionEvent?): Boolean {
            // 一次单独的轻击抬起事件
            Log.e("view13", "onSingleTapUp")
            return true
        }

        override fun onDown(e: MotionEvent?): Boolean {
            // 按下屏幕就会触发
            Log.e("view13", "onDown")
            return true
        }

        override fun onFling(
            e1: MotionEvent?,
            e2: MotionEvent?,
            velocityX: Float,
            velocityY: Float
        ): Boolean {
            // 滑屏事件   用户按下 滑动后在松开
            Log.e("view13", "onFling")
            return true
        }

        override fun onScroll(
            e1: MotionEvent?,
            e2: MotionEvent?,
            distanceX: Float,
            distanceY: Float
        ): Boolean {
            // 滑动事件  在屏幕上拖动控件或者以抛的动作 都会多次触发
            Log.e("view13", "onScroll")
            return true
        }

    }

    class DoubleTapListener : GestureDetector.OnDoubleTapListener{
        override fun onDoubleTap(e: MotionEvent?): Boolean {
            // 双击事件
            Log.e("view13", "onDoubleTap")
            return true
        }

        override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
            // 双击间隔中发生的事件  包含 down up move
            Log.e("view13", "onDoubleTapEvent")
            return true
        }

        override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
            // 用于判断是否是单击还是双击事件
            Log.e("view13", "onSingleTapConfirmed")
            return true        }

    }

}

Window 与 WindowManager

WindowManager 可通过getSystemService(Context.WINDOW_SERVICE)
WindowManager.addView(View view, ViewGroup.LayoutParams params) 可向界面添加view
WindowManager.LayoutParams 构造函数
public LayoutParams(int w, int h, int _type, int _flags, int _format)
type 用来表示 Window 的类型
window 有三种类型
应用window 1-99
子window 1000-1999
系统window 2000-2999
层级大的window会覆盖在层级小的window上面

_flags 用来控制window 的显示特性  常用的几个选项
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL     表示window区域内的事件自己处理 区域外的时间传递给底层的window处理
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE       表示window不需要获取焦点 不接收各种输入事件
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED    让window显示在锁屏上


// 不设置这个弹出框的透明遮罩显示为黑色
params.format = PixelFormat.TRANSLUCENT;
class MainActivity13Windowmanager : AppCompatActivity(), View.OnClickListener,
    View.OnTouchListener {

    // 延迟初始化变量
    lateinit var mImageView : ImageView
    lateinit var mLayoutparams : WindowManager.LayoutParams
    lateinit var mWindowManager : WindowManager


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main13windowmanager)

        // 清单文件添加权限 与 申请权限
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
            var intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
            startActivityForResult(intent ,100)
        }else{
            init()
        }

    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if(requestCode == 100){
            init()
        }
    }

    fun init(){
        btn_add.setOnClickListener(this)
        btn_remove.setOnClickListener(this)

        mWindowManager = applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    }


    override fun onClick(v: View?) {

        when (v?.id){
            R.id.btn_add ->{
                // 添加控件
                mImageView = ImageView(this)
                mImageView.setBackgroundResource(R.mipmap.ic_launcher)
                mLayoutparams = WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT ,WindowManager.LayoutParams.WRAP_CONTENT ,
                    2099 ,
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED ,
                    PixelFormat.TRANSPARENT)

                mLayoutparams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR

                // 设置初始位置
                mLayoutparams.gravity = Gravity.TOP or Gravity.LEFT
                mLayoutparams.x = 0
                mLayoutparams.y = 300

                mImageView.setOnTouchListener(this)
                mWindowManager.addView(mImageView , mLayoutparams)
            }

            R.id.btn_remove ->{
                // 移除控件
                mImageView?.let {
                    mWindowManager.removeViewImmediate(mImageView)
                }
            }
        }
    }

    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        //监听触摸事件 让添加的view 跟随手指移动
        event?.let {
            mImageView?.let {
                var rawX = event.rawX
                var rawY = event.rawY
                when (event.action){
                    MotionEvent.ACTION_MOVE -> {
                        mLayoutparams.x = rawX.toInt() - mImageView.width/2
                        mLayoutparams.y = rawY.toInt() - mImageView.height
                        mWindowManager.updateViewLayout(mImageView , mLayoutparams)
                    }

                }
            }

        }
        return false
    }

}
类似小火箭控件.gif

你可能感兴趣的:(android自定义view【控件篇】)