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

2021-03-02  本文已影响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)

        }

    }

}

上一篇下一篇

猜你喜欢

热点阅读