? ? ? ? 1、今天做一個(gè)繼承于View的自定義View 餅狀圖(canvas.drawArc)
? ? ? ? 同樣,開(kāi)始先創(chuàng)建一個(gè)CustomEmptyView繼承View,并實(shí)現(xiàn)構(gòu)造方法和onDraw方法

? ? ? ? 定義一個(gè)Paint參數(shù)
var paint=Paint()
? ? ? ? 在onDraw方法中,實(shí)現(xiàn)繪制一個(gè)扇形drawArc,先看一下Canvas.drawArc方法需要的參數(shù)

? ? ? ? 能看到最終都是調(diào)用的含有l(wèi)eft、top、right、bottom參數(shù)的方法,那我們直接就按照這個(gè)傳參調(diào)用drawArc方法,在onDraw中添加如下代碼
paint.color=Color.BLACK
paint.style=Paint.Style.FILL
canvas!!.drawArc(200f,200f,600f,600f,0f, 90f,true,paint)
把該自定義View在xml文件中引用,運(yùn)行后效果如下

這里再說(shuō)一下,在Android中,坐標(biāo)系y軸正方向是向下的,所以從0度開(kāi)始繪制到90度,就是上圖中的樣式。????
? ? ? ? 接下來(lái)修改paint的顏色為紅色,繪制一個(gè)跨度為54度的扇形
paint.color = Color.RED
canvas!!.drawArc(198f, 200f, 598f, 600f, 90f, 54f, true, paint)
運(yùn)行后,效果如圖

? ? ? ? 同理,我們可以做一個(gè)藍(lán)色的跨度是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中的參數(shù)以外啥都沒(méi)做,這是個(gè)如此簡(jiǎn)單的自定義View。
????????我們可以修改為,餅狀圖的扇形個(gè)數(shù)、扇形顏色、每個(gè)扇形區(qū)域所占用的比例等信息為傳入的參數(shù)(這里沒(méi)有實(shí)現(xiàn)數(shù)據(jù)當(dāng)中的一對(duì)一,尤其是顏色和數(shù)據(jù)之間的對(duì)應(yīng)關(guān)系),那么就可以修改為如下


我們?cè)趚ml中給這個(gè)view一個(gè)id? custom_empty,然后在activity中,把需要的這些參數(shù)傳入

運(yùn)行之后,看下效果

依然如此簡(jiǎn)單。當(dāng)然我們可以根據(jù)動(dòng)畫(huà),canvas的其他draw方法來(lái)實(shí)現(xiàn)其他的功能。
? ? ? ? 2、實(shí)現(xiàn)繼承ViewGroup的一個(gè)左右滑動(dòng)的自定義View
? ? ? ? 一個(gè)View的繪制流程一般分三個(gè)步驟:onMeasure(計(jì)算測(cè)量),onLayout(布局位置),onDraw(繪制)
? ? ? ? 而在onMeasure階段,有個(gè)MeasureSpec參數(shù),這個(gè)參數(shù)包括兩個(gè)值mode和size,需要根據(jù)不同的mode來(lái)做不同的測(cè)量children的不同處理,參考文檔:深入理解MeasureSpec - 簡(jiǎn)書(shū)
? ? ? ? 首先還是先創(chuàng)建一個(gè)CustomViewGroupView類(lèi),繼承ViewGroup,實(shí)現(xiàn)構(gòu)造方法并重寫(xiě)onLayout方法

? ? ? ? 下一步,對(duì)控件進(jìn)行測(cè)量,重寫(xiě)onMeasure方法

? ? ? ? 在onMeasure方法中,首先獲取到寬高的mode和size
val widthMode=MeasureSpec.getMode(widthMeasureSpec)
val heightMode=MeasureSpec.getMode(heightMeasureSpec)
val widthSize=MeasureSpec.getSize(widthMeasureSpec)
val heightSize=MeasureSpec.getSize(heightMeasureSpec)
然后需要根據(jù)參數(shù)widthMeasureSpec和widthMeasureSpec去測(cè)量子View的大小
measureChildren(widthMeasureSpec,heightMeasureSpec)
再然后,需要根據(jù)寬高的mode來(lái)做不同的測(cè)量children的處理
if (childCount ==0) {
? ? ? ?setMeasuredDimension(0, 0)
}else if (widthMode == MeasureSpec.AT_MOST && heightMode == ????MeasureSpec.AT_MOST) {
//我們做一個(gè)類(lèi)似于ViewPager可左右滑動(dòng)的ViewGroup,寬高的mode都是AT_MOST,那
//么我們可以設(shè)定寬度是所有子View的寬度的和,也就是widthOne*childCount,高度設(shè)置
//為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)
}
我們看到對(duì)不同的mode進(jìn)行不同處理的時(shí)候,都調(diào)用了setMeasuredDimension這個(gè)方法,這個(gè)方法其實(shí)就是來(lái)決定當(dāng)前View的大小的方法。這樣的話,View的onMeasure過(guò)程就結(jié)束了。??
? ? ? ? 接下來(lái)就是設(shè)置View的onLayout方法,先來(lái)思考一下怎么處理,我們按照自定義ViewGroup來(lái)顯示4張圖片,并且可以左右滑動(dòng),對(duì)于ViewGroup的onLayout方法,其實(shí)也就是對(duì)每個(gè)子View進(jìn)行l(wèi)ayout方法,在onMeasure方法中,我們是把四張圖片左右連接到一塊的,一張挨著一張,如此,子View的layout方法我們也就明了應(yīng)該怎么設(shè)置了
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
}
接下來(lái),我們就需要來(lái)實(shí)現(xiàn)左右滑動(dòng)了,用Gesturedetector來(lái)監(jiān)聽(tīng)手勢(shì)識(shí)別detector,并且重寫(xiě)onTouchEvent事件,實(shí)現(xiàn)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


? ? ? ? 運(yùn)行看下效果

雖然說(shuō)可以滑動(dòng)了,但是整體效果來(lái)說(shuō)呢,離理想中的ViewPager還有段距離,它只能跟隨手勢(shì)滑動(dòng),但還達(dá)不到翻頁(yè)的效果,我們需要實(shí)現(xiàn)的是有些彈性的滑動(dòng),滑動(dòng)超過(guò)屏幕的一半顯示下一頁(yè),不超過(guò)就還是顯示這一頁(yè)。那么我們?cè)O(shè)置幾個(gè)參數(shù)
private var _scrollX =0//用來(lái)記錄上次的滑動(dòng)距離
private var position =0//用來(lái)顯示現(xiàn)在展示的圖片的下標(biāo)
private var imageNum =0//子View的個(gè)數(shù)
private var childWidth=0//單個(gè)子View的寬度
再然后在onTouchEvent方法中處理Move、up
when (event!!.action) {
????MotionEvent.ACTION_UP -> {
????????scrollTo(position*childWidth,0)
????}
????MotionEvent.ACTION_MOVE -> {
????????_scrollX =scrollX//getScrollX()方法獲取到的是滑動(dòng)的相對(duì)距離
? ? ? ? position = (_scrollX +childWidth /2) /childWidth
? ? ? ? if (position >=imageNum) {
????????????position =imageNum -1
? ? ? ? }
????????if (position <0) {
????????????position =0
? ? ? ? }
????}
}
在onLayout中獲取childWidth和imageNum
imageNum=childCount
childWidth = child.measuredWidth
再運(yùn)行下,看下效果

這樣就大體達(dá)到了ViewPager滑動(dòng)進(jìn)行頁(yè)面切換的要求。
最后附上整體代碼,望指正與交流
class CustomViewGroupView : ViewGroup {
????constructor(context: Context) :super(context)
????constructor(context: Context, attributeSet: AttributeSet) :super(context, attributeSet)
????private var _scrollX =0//用來(lái)記錄上次的滑動(dòng)距離
? ? private var position =0//用來(lái)顯示現(xiàn)在展示的圖片的下標(biāo)
? ? private var imageNum =0//子View的個(gè)數(shù)
? ? private var childWidth=0//單個(gè)子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()方法獲取到的是滑動(dòng)的相對(duì)距離
? ? ? ? ? ? ? ? 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) {
????????????//我們做一個(gè)類(lèi)似于ViewPager可左右滑動(dòng)的ViewGroup,寬高的mode都
????????????//是AT_MOST,那么我們可以設(shè)定寬度
? ? ? ? ? ? //是所有子View的寬度的和,也就是widthOne*childCount,高度設(shè)置為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)
????????}
????}
}