国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

一步步實現(xiàn)自定義View之播放暫??丶?/h1>
2019-10-21 21:49:06
字體:
供稿:網(wǎng)友

最近開始深入學(xué)習(xí)自定義View,通過模仿學(xué)習(xí),再配合Kotlin,寫了一些自定義控件,這次介紹的是類似于音樂播放暫停的一個控件

首先看一下效果圖:

下面先分析一下原理:

View,播放暫停控件

狀態(tài)1是播放狀態(tài),有兩個小矩形,外面是一個圓,它需要最終變換成狀態(tài)3的暫停狀態(tài) 
狀態(tài)2是兩個小矩形變成如圖的黑色三角的一個過程 
我們可以通過動畫來實現(xiàn)它,兩個小矩形分別變成三角形的一半 
同時再給畫布一個90度的旋轉(zhuǎn)

具體實現(xiàn):

1.繼承View

class PlayPauseView : View

2.重寫構(gòu)造函數(shù)

constructor(context: Context?) : this(context,null)constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs,0)constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){ init(context!!,attrs!!)}

一般的寫法都是講初始化代碼放在三個參數(shù)的構(gòu)造函數(shù)里,其它兩個構(gòu)造函數(shù)分別繼承自參數(shù)更多的一個本類的構(gòu)造函數(shù),所以這里用的是this關(guān)鍵字

3.初始化參數(shù) 

首先我們需要先在values包的attrs文件中先聲明屬性

<declare-styleable name="PlayPauseView"> <attr name="barWidth" format="dimension"/> <attr name="barHeight" format="dimension"/> <attr name="barPadding" format="dimension"/> <attr name="barColor" format="color"/> <attr name="barBgColor" format="color"/> <attr name="barClockWise" format="boolean"/> <attr name="barPlayingState" format="boolean"/></declare-styleable>

然后在構(gòu)造函數(shù)中拿到這些參數(shù)

mBarWidth = typedArray.getDimension(R.styleable.PlayPauseView_barWidth,10 * getDensity())mBarHeight = typedArray.getDimension(R.styleable.PlayPauseView_barHeight,30 * getDensity())mPadding = typedArray.getDimension(R.styleable.PlayPauseView_barPadding,10 * getDensity())//可以通過上面的三個參數(shù)計算出下面的參數(shù)值,所以不再通過xml設(shè)置mBarSpace = mBarHeight - mBarWidth * 2mRadius = mBarWidth + mBarSpace.div(2) + mPaddingmWidth = mRadius * 2mWidth = mRadius * 2

mBarWidth 是小矩形的寬度,mBarHeight 是小矩形的高度,mPadding 是小矩形距離整個view的邊界距離(參考上圖中狀態(tài)1中左邊小矩形距離大矩形的距離,距離top和left應(yīng)該是一樣的,這個值就是mPadding )。

mBarSpace 是兩個小矩形之間的距離,mRadius 是狀態(tài)1中圓的半徑,mWidth 、mWidth 是狀態(tài)1中大矩形的寬高。(這些參數(shù)都是通過上面三個參數(shù)計算出來的)

同樣的在初始化這一步,初始化畫筆和兩個小矩形(半三角)Path

mBgPaint = Paint(Paint.ANTI_ALIAS_FLAG)mBgPaint!!.color = mBgColormBgPaint!!.style = Paint.Style.FILLmBarPaint = Paint(Paint.ANTI_ALIAS_FLAG)mBarPaint!!.color = mBarColormBarPaint!!.style = Paint.Style.FILLmLeftPath = Path()mRightPath = Path()

同時通過動畫使矩形變成三角的參數(shù) mProgress,在onDraw中會用到

4.測量控件

在onMeasure方法中測量控件的寬高,主要是在xml中wrap_content或者具體數(shù)值的時候

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val widthMode = MeasureSpec.getMode(widthMeasureSpec) val heightMode = MeasureSpec.getMode(heightMeasureSpec) val measureWidth = MeasureSpec.getSize(widthMeasureSpec) val measureHeight = MeasureSpec.getSize(heightMeasureSpec) when(widthMode){  MeasureSpec.EXACTLY ->{  mWidth = Math.min(measureWidth,measureHeight).toFloat()  mHeight = Math.min(measureWidth,measureHeight).toFloat()  setMeasuredDimension(mWidth.toInt(),mHeight.toInt())  }  MeasureSpec.AT_MOST -> {  mWidth = mRadius * 2  mHeight = mRadius * 2  setMeasuredDimension(mWidth.toInt(),mHeight.toInt())  }  MeasureSpec.UNSPECIFIED -> {  } } }

5.繪制

override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) //需要重新設(shè)置,否則畫出來的圖形會保留上一次的 mLeftPath!!.rewind() mRightPath!!.rewind() mRadius = mWidth.div(2) //先畫一個圓 canvas!!.drawCircle(mWidth.div(2),mHeight.div(2),mRadius,mBgPaint) //核心代碼 //順時針 if(isClockWise){  mLeftPath!!.moveTo(mPadding + (mBarWidth + mBarSpace.div(2)) * mProgress ,mPadding)  mLeftPath!!.lineTo(mPadding ,mPadding + mBarHeight)  mLeftPath!!.lineTo(mPadding + mBarWidth + mBarSpace.div(2) * mProgress,mPadding + mBarHeight)  mLeftPath!!.lineTo(mPadding + mBarWidth + mBarSpace.div(2) * mProgress,mPadding)  mLeftPath!!.close()  mRightPath!!.moveTo(mPadding + mBarWidth + mBarSpace - mBarSpace.div(2) * mProgress,mPadding)  mRightPath!!.lineTo(mPadding + mBarWidth + mBarSpace - mBarSpace.div(2) * mProgress,mPadding + mBarHeight)  mRightPath!!.lineTo(mPadding + mBarWidth * 2 + mBarSpace ,mPadding + mBarHeight)  mRightPath!!.lineTo(mPadding + mBarWidth * 2 + mBarSpace - (mBarWidth + mBarSpace.div(2)) * mProgress,mPadding)  mRightPath!!.close() } //逆時針 else{  mLeftPath!!.moveTo(mPadding,mPadding)  mLeftPath!!.lineTo(mPadding + (mBarWidth + mBarSpace.div(2)) * mProgress,mPadding + mBarHeight)  mLeftPath!!.lineTo(mPadding + mBarWidth + mBarSpace.div(2) * mProgress,mPadding + mBarHeight)  mLeftPath!!.lineTo(mPadding + mBarWidth + mBarSpace.div(2) * mProgress,mPadding)  mLeftPath!!.close()  mRightPath!!.moveTo(mPadding + mBarWidth + mBarSpace - mBarSpace.div(2) * mProgress,mPadding)  mRightPath!!.lineTo(mPadding + mBarWidth + mBarSpace - mBarSpace.div(2) * mProgress,mPadding + mBarHeight)  mRightPath!!.lineTo(mPadding + mBarWidth * 2 + mBarSpace - (mBarWidth + mBarSpace.div(2)) * mProgress,mPadding + mBarHeight)  mRightPath!!.lineTo(mPadding + mBarWidth * 2 + mBarSpace,mPadding)  mRightPath!!.close() } var corner = 0 if(isClockWise){  corner = 90 }else{  corner = -90 } val rotation = corner * mProgress //旋轉(zhuǎn)畫布 canvas.rotate(rotation,mWidth.div(2),mHeight.div(2)) canvas.drawPath(mLeftPath!!,mBarPaint) canvas.drawPath(mRightPath!!,mBarPaint) }

通過這張圖來看一下核心代碼(順時針) 
A點的坐標(biāo)(mPadding + (mBarWidth + mBarSpace.div(2)) * mProgress ,mPadding) 
mPadding 是小矩形距離大矩形的距離,A點最終會到F點,兩者相差一個矩形 + 兩個矩形間隔/2的距離(就是 mBarWidth + mBarSpace.div(2) 的距離),通過乘以一個從0到1的mProgress的變化即可 
同理可得 D到F,B到E,C到E的變化坐標(biāo)

右側(cè)的矩形也是如此計算,如果是逆時針旋轉(zhuǎn),三角形是倒過來的,原理也是一樣的

6.動畫 

上面提到過我們需要一個從0到1的mProgress的變化(從播放到暫停),或者需要一個從1到0的mProgress(從暫停到播放)

動畫核心代碼如下:

val valueAnimator = ValueAnimator.ofFloat(if (isPlaying) 1f else 0f, if (isPlaying) 0f else 1f)valueAnimator.duration = 200 valueAnimator.addUpdateListener { mProgress = it.animatedValue as Float invalidate() } return valueAnimator

mProgress 不斷地變化,然后調(diào)用invalidate(),不斷地調(diào)用onDraw()方法

7.監(jiān)聽

setOnClickListener { if(isPlaying){ pause() mPlayPauseListener!!.pause() }else{ play() mPlayPauseListener!!.play() }}private fun play() { getAnimator().cancel() setPlaying(true) getAnimator().start() }private fun pause() { getAnimator().cancel() setPlaying(false) getAnimator().start()}

mPlayPauseListener是對外提供的接口,可以在Activity中拿到播放或者暫停的狀態(tài),以供我們下一步的操作

8.使用

最后附上這個自定義View目前有的屬性:

app:barHeight="30dp"//矩形條的寬度app:barWidth="10dp"//矩形條的高度app:barPadding="20dp"//矩形條距離原點(邊界)的距離app:barClockWise="true"//是否是順時針轉(zhuǎn)動app:barPlayingState="false"//默認(rèn)的狀態(tài),播放或者暫停app:barBgColor="@color/colorRed"//控件背景色app:barColor="@color/black"//按鈕顏色

在Activity或者Fragment中的使用:

 val playPauseView = findViewById<PlayPauseView>(R.id.play_pause_view) //控件的點擊事件 playPauseView.setPlayPauseListener(this) //需要實現(xiàn)的方法 override fun play() { Toast.makeText(this,"現(xiàn)在處于播放狀態(tài)",Toast.LENGTH_SHORT).show() } override fun pause() { Toast.makeText(this,"現(xiàn)在處于暫停狀態(tài)",Toast.LENGTH_SHORT).show() }

至此,這個自定義View大致上完成了,還有一些細(xì)節(jié)就不再這里細(xì)說了。如果你有興趣深入了解,可以看一下這里:自定義View集合中的PlayPauseView,如果能隨手點個Star也是極好的。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持VEVB武林網(wǎng)。


注:相關(guān)教程知識閱讀請移步到Android開發(fā)頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表

主站蜘蛛池模板: 米脂县| 弋阳县| 石棉县| 宁城县| 双桥区| 铅山县| 鹿泉市| 沙田区| 高平市| 乳山市| 报价| 长丰县| 花莲市| 锡林郭勒盟| 南丹县| 西青区| 科尔| 南安市| 安新县| 绥阳县| 巴塘县| 淮北市| 华亭县| 安远县| 那坡县| 叙永县| 玛沁县| 永寿县| 白沙| 吉林市| 颍上县| 巩义市| 富裕县| 苏尼特右旗| 乌拉特前旗| 托克托县| 吉林省| 四川省| 肥乡县| 肇东市| 陵水|