美文网首页
Android 自定义View(六)实现继承View/ViewG

Android 自定义View(六)实现继承View/ViewG

作者: 行走世间全都是妖怪 | 来源:发表于2021-03-02 17:44 被阅读0次

            1、今天做一个继承于View的自定义View 饼状图(canvas.drawArc)

            同样,开始先创建一个CustomEmptyView继承View,并实现构造方法和onDraw方法

            定义一个Paint参数

    var paint=Paint()

            在onDraw方法中,实现绘制一个扇形drawArc,先看一下Canvas.drawArc方法需要的参数

            能看到最终都是调用的含有left、top、right、bottom参数的方法,那我们直接就按照这个传参调用drawArc方法,在onDraw中添加如下代码

    paint.color=Color.BLACK

    paint.style=Paint.Style.FILL

    canvas!!.drawArc(200f,200f,600f,600f,0f, 90f,true,paint)

    把该自定义View在xml文件中引用,运行后效果如下

    这里再说一下,在Android中,坐标系y轴正方向是向下的,所以从0度开始绘制到90度,就是上图中的样式。    

            接下来修改paint的颜色为红色,绘制一个跨度为54度的扇形

    paint.color = Color.RED

    canvas!!.drawArc(198f, 200f, 598f, 600f, 90f, 54f, true, paint)

    运行后,效果如图

            同理,我们可以做一个蓝色的跨度是66度的扇形

    paint.color=Color.BLUE

    canvas!!.drawArc(198f,198f,598f,598f,144f,66f,true,paint)

              绿色,跨度是150度的扇形

    paint.color=Color.GREEN

    canvas!!.drawArc(200f,198f,600f,598f,210f,150f,true,paint)

    整体的效果图:

            可以看到在上面的代码中,除了修改paint的颜色值以及修改了drawArc中的参数以外啥都没做,这是个如此简单的自定义View。

            我们可以修改为,饼状图的扇形个数、扇形颜色、每个扇形区域所占用的比例等信息为传入的参数(这里没有实现数据当中的一对一,尤其是颜色和数据之间的对应关系),那么就可以修改为如下

    我们在xml中给这个view一个id  custom_empty,然后在activity中,把需要的这些参数传入

    运行之后,看下效果

    依然如此简单。当然我们可以根据动画,canvas的其他draw方法来实现其他的功能。

            2、实现继承ViewGroup的一个左右滑动的自定义View

            一个View的绘制流程一般分三个步骤:onMeasure(计算测量),onLayout(布局位置),onDraw(绘制)

            而在onMeasure阶段,有个MeasureSpec参数,这个参数包括两个值mode和size,需要根据不同的mode来做不同的测量children的不同处理,参考文档:深入理解MeasureSpec - 简书

            首先还是先创建一个CustomViewGroupView类,继承ViewGroup,实现构造方法并重写onLayout方法

            下一步,对控件进行测量,重写onMeasure方法

            在onMeasure方法中,首先获取到宽高的mode和size

    val widthMode=MeasureSpec.getMode(widthMeasureSpec)

    val heightMode=MeasureSpec.getMode(heightMeasureSpec)

    val widthSize=MeasureSpec.getSize(widthMeasureSpec)

    val heightSize=MeasureSpec.getSize(heightMeasureSpec)

    然后需要根据参数widthMeasureSpec和widthMeasureSpec去测量子View的大小

    measureChildren(widthMeasureSpec,heightMeasureSpec)

    再然后,需要根据宽高的mode来做不同的测量children的处理

    if (childCount ==0) {

           setMeasuredDimension(0, 0)

    }else if (widthMode == MeasureSpec.AT_MOST && heightMode ==     MeasureSpec.AT_MOST) {

    //我们做一个类似于ViewPager可左右滑动的ViewGroup,宽高的mode都是AT_MOST,那

    //么我们可以设定宽度是所有子View的宽度的和,也就是widthOne*childCount,高度设置

    //为heightOne

        val widthOne = getChildAt(0).measuredWidth

        val heightOne = getChildAt(0).measuredHeight

        setMeasuredDimension (widthOne *childCount, heightOne)

    }else if (widthMode == MeasureSpec.AT_MOST) {

        val widthOne = getChildAt(0).measuredWidth

        setMeasuredDimension (widthOne*childCount, heightSize)

    }else if (heightMode == MeasureSpec.AT_MOST) {

        val heightOne = getChildAt(0).measuredHeight

        setMeasuredDimension (widthSize, heightOne)

    }

    我们看到对不同的mode进行不同处理的时候,都调用了setMeasuredDimension这个方法,这个方法其实就是来决定当前View的大小的方法。这样的话,View的onMeasure过程就结束了。  

            接下来就是设置View的onLayout方法,先来思考一下怎么处理,我们按照自定义ViewGroup来显示4张图片,并且可以左右滑动,对于ViewGroup的onLayout方法,其实也就是对每个子View进行layout方法,在onMeasure方法中,我们是把四张图片左右连接到一块的,一张挨着一张,如此,子View的layout方法我们也就明了应该怎么设置了

    var child:View

    var left =0

    for (indexin 0 until childCount) {

        child=getChildAt(index)

        val width = child.measuredWidth

        child.layout(left,0,left+width,b)

        left+=width

    }

    接下来,我们就需要来实现左右滑动了,用Gesturedetector来监听手势识别detector,并且重写onTouchEvent事件,实现detector.onTouchEvent(event)(我们先做onScroll事件处理)

    private val detector =

    GestureDetector(object : GestureDetector.OnGestureListener {

    ...

    override fun onScroll(

                e1: MotionEvent,

                e2: MotionEvent,

                distanceX: Float,

                distanceY: Float

    ): Boolean {

        scrollBy(distanceX.toInt(), 0)

        return false

     }

    ...

    })

    然后在xml布局中引用CustomViewGroupView,并添加四张Image

            运行看下效果

    虽然说可以滑动了,但是整体效果来说呢,离理想中的ViewPager还有段距离,它只能跟随手势滑动,但还达不到翻页的效果,我们需要实现的是有些弹性的滑动,滑动超过屏幕的一半显示下一页,不超过就还是显示这一页。那么我们设置几个参数

    private var _scrollX =0//用来记录上次的滑动距离

    private var position =0//用来显示现在展示的图片的下标

    private var imageNum =0//子View的个数

    private var childWidth=0//单个子View的宽度

    再然后在onTouchEvent方法中处理Move、up

    when (event!!.action) {

        MotionEvent.ACTION_UP -> {

            scrollTo(position*childWidth,0)

        }

        MotionEvent.ACTION_MOVE -> {

            _scrollX =scrollX//getScrollX()方法获取到的是滑动的相对距离

            position = (_scrollX +childWidth /2) /childWidth

            if (position >=imageNum) {

                position =imageNum -1

            }

            if (position <0) {

                position =0

            }

        }

    }

    在onLayout中获取childWidth和imageNum

    imageNum=childCount

    childWidth = child.measuredWidth

    再运行下,看下效果

    这样就大体达到了ViewPager滑动进行页面切换的要求。

    最后附上整体代码,望指正与交流

    class CustomViewGroupView : ViewGroup {

        constructor(context: Context) :super(context)

        constructor(context: Context, attributeSet: AttributeSet) :super(context, attributeSet)

        private var _scrollX =0//用来记录上次的滑动距离

        private var position =0//用来显示现在展示的图片的下标

        private var imageNum =0//子View的个数

        private var childWidth=0//单个子View的宽度

        private val detector =

            GestureDetector(object : GestureDetector.OnGestureListener {

                override fun onDown(e: MotionEvent): Boolean {

                return false

                }

                override fun onShowPress(e: MotionEvent) {}

                override fun onSingleTapUp(e: MotionEvent): Boolean {

                    return false

                }

                override fun onScroll(

                    e1: MotionEvent,

                    e2: MotionEvent,

                    distanceX: Float,

                    distanceY: Float

                    ): Boolean {

                        scrollBy(distanceX.toInt(), 0)

                    return false

                }

                override fun onLongPress(e: MotionEvent) {}

                override fun onFling(

                    e1: MotionEvent,

                    e2: MotionEvent,

                    velocityX: Float,

                    velocityY: Float

                        ): Boolean {

                        return false

                }

        })

        override fun onTouchEvent(event: MotionEvent?): Boolean {

            detector.onTouchEvent(event)

            when (event!!.action) {

                MotionEvent.ACTION_DOWN -> {

                }

                MotionEvent.ACTION_UP -> {

                    scrollTo(position*childWidth,0)

                }

                MotionEvent.ACTION_MOVE -> {

                    _scrollX =scrollX//getScrollX()方法获取到的是滑动的相对距离

                    position = (_scrollX +childWidth /2) /childWidth

                    if (position >=imageNum) {

                        position =imageNum -1

                    }

                    if (position <0) {

                        position =0

                    }

            }

        }

            return true

        }

        override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {

            var child: View

            var left =0

            for (indexin 0 until childCount) {

                child = getChildAt(index)

                childWidth = child.measuredWidth

                child.layout(left, 0, left +childWidth, b)

                left +=childWidth

            }

        }

        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)

            measureChildren(widthMeasureSpec, heightMeasureSpec)

            imageNum=childCount

            if (childCount ==0) {

                setMeasuredDimension(0, 0)

            }else if (widthMode == MeasureSpec.AT_MOST && heightMode ==                     MeasureSpec.AT_MOST) {

                //我们做一个类似于ViewPager可左右滑动的ViewGroup,宽高的mode都

                //是AT_MOST,那么我们可以设定宽度

                //是所有子View的宽度的和,也就是widthOne*childCount,高度设置为heightOne

                val widthOne = getChildAt(0).measuredWidth

                val heightOne = getChildAt(0).measuredHeight

                setMeasuredDimension(widthOne *childCount, heightOne)

            }else if (widthMode == MeasureSpec.AT_MOST) {

                val widthOne = getChildAt(0).measuredWidth

                setMeasuredDimension(widthOne *childCount, heightSize)

            }else if (heightMode == MeasureSpec.AT_MOST) {

                val heightOne = getChildAt(0).measuredHeight

                setMeasuredDimension(widthSize, heightOne)

            }

        }

    }

    相关文章

      网友评论

          本文标题:Android 自定义View(六)实现继承View/ViewG

          本文链接:https://www.haomeiwen.com/subject/xtiafltx.html