一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Android - 一步步實現自定義View之播放暫停控件

一步步實現自定義View之播放暫停控件

2022-03-07 14:46ckwccc Android

本文教大家一步步實現自定義View之播放暫停控件,具有一定的參考價值,感興趣的小伙伴們可以參考一下

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

首先看一下效果圖:

一步步實現自定義View之播放暫停控件

下面先分析一下原理:

一步步實現自定義View之播放暫停控件

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

具體實現:

1.繼承View

?
1
class PlayPauseView : View

2.重寫構造函數

?
1
2
3
4
5
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!!)
}

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

3.初始化參數

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

?
1
2
3
4
5
6
7
8
9
<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>

然后在構造函數中拿到這些參數

?
1
2
3
4
5
6
7
8
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())
//可以通過上面的三個參數計算出下面的參數值,所以不再通過xml設置
mBarSpace = mBarHeight - mBarWidth * 2
mRadius = mBarWidth + mBarSpace.div(2) + mPadding
mWidth = mRadius * 2
mWidth = mRadius * 2

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

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

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

?
1
2
3
4
5
6
7
8
9
10
mBgPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mBgPaint!!.color = mBgColor
mBgPaint!!.style = Paint.Style.FILL
 
mBarPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mBarPaint!!.color = mBarColor
mBarPaint!!.style = Paint.Style.FILL
 
mLeftPath = Path()
mRightPath = Path()

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

4.測量控件

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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.繪制

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
override fun onDraw(canvas: Canvas?) {
 super.onDraw(canvas)
 //需要重新設置,否則畫出來的圖形會保留上一次的
 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
 //旋轉畫布
 canvas.rotate(rotation,mWidth.div(2),mHeight.div(2))
 
 canvas.drawPath(mLeftPath!!,mBarPaint)
 canvas.drawPath(mRightPath!!,mBarPaint)
 }

一步步實現自定義View之播放暫停控件

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

右側的矩形也是如此計算,如果是逆時針旋轉,三角形是倒過來的,原理也是一樣的

6.動畫

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

動畫核心代碼如下:

?
1
2
3
4
5
6
7
8
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 不斷地變化,然后調用invalidate(),不斷地調用onDraw()方法

7.監聽

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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中拿到播放或者暫停的狀態,以供我們下一步的操作

8.使用

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

?
1
2
3
4
5
6
7
app:barHeight="30dp"//矩形條的寬度
app:barWidth="10dp"//矩形條的高度
app:barPadding="20dp"//矩形條距離原點(邊界)的距離
app:barClockWise="true"//是否是順時針轉動
app:barPlayingState="false"//默認的狀態,播放或者暫停
app:barBgColor="@color/colorRed"//控件背景色
app:barColor="@color/black"//按鈕顏色

在Activity或者Fragment中的使用:

?
1
2
3
4
5
6
7
8
9
10
11
12
val playPauseView = findViewById<PlayPauseView>(R.id.play_pause_view)
//控件的點擊事件
playPauseView.setPlayPauseListener(this)
 
//需要實現的方法
override fun play() {
Toast.makeText(this,"現在處于播放狀態",Toast.LENGTH_SHORT).show()
}
 
override fun pause() {
Toast.makeText(this,"現在處于暫停狀態",Toast.LENGTH_SHORT).show()
}

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:https://blog.csdn.net/ckwccc/article/details/80761974

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 操穴勤| 日本老师xxxxx18 | 精品国产国偷自产在线观看 | 故意短裙公车被强好爽在线播放 | 久久精品观看 | 日韩大片免费观看 | 天堂久久久久va久久久久 | 51精品| 精品视频手机在线观看免费 | 手机看片1024国产 | 嫩草精品 | 忘忧草研究院一二三 | 女人和拘做受全过程免费 | 特黄特黄一级高清免费大片 | 白丝校花被扒开双腿喷水小说 | www亚洲色图 | 狠狠的撞进去嗯啊h女强男视频 | 欧美成人乱弄视频 | 久久视热频国产这里只有精品23 | 99av导航| 99精品久久精品一区二区小说 | 免费看成年视频网页 | 欧美人xxxxxbbbb | 日韩在线资源 | 欧美日韩国产一区二区三区伦 | 日本三级大学生17 | 国产美女亚洲精品久久久久久 | 91热国内精品永久免费观看 | 小小水蜜桃视频高清在线观看免费 | 色天天色综合 | 91久久综合九色综合欧美98 | 无人视频在线观看完整版高清 | 国产在线影院 | 女仆色在线观看 | 四虎国产精品视频免费看 | 99九九精品免费视频观看 | 我与白丝同桌的故事h文 | 激情婷婷成人亚洲综合 | japanesexxxx在线播放 | 精品一区二区三区中文 | bnb998八度免费影院丫意浓 |