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)
}
}
}