Android 自定義View(六)實(shí)現(xiàn)繼承View/ViewGroup的自定義view

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

????????}

????}

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容