一步步实现自定义View之播放暂停控件

最近开始深入学习自定义View,通过模仿学习,再配合Kotlin,写了一些自定义控件,这次介绍的是类似于音乐播放暂停的一个控件

首先看一下效果图:

下面先分析一下原理:

状态1是播放状态,有两个小矩形,外面是一个圆,它需要最终变换成状态3的暂停状态
状态2是两个小矩形变成如图的黑色三角的一个过程
我们可以通过动画来实现它,两个小矩形分别变成三角形的一半
同时再给画布一个90度的旋转

具体实现:

1.继承View

class PlayPauseView : View

2.重写构造函数

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文件中先声明属性

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

然后在构造函数中拿到这些参数

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

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或者具体数值的时候

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)
 //需要重新设置,否则画出来的图形会保留上一次的
 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)
 }

通过这张图来看一下核心代码(顺时针)
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(从暂停到播放)

动画核心代码如下:

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.监听

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目前有的属性:

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中的使用:

 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也是极好的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android使用VideoView播放本地视频和网络视频的方法

    1.效果展示 2.布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="matc

  • 解决video标签在安卓webview下无法自动播放问题

    在安卓webview下 html5 的 video 设置autoplay 属性 或在document ready中使用play方法 都不能使它自动播放 只能用webview的onPageFinished方法来解决此问题,代码如下: 复制代码 代码如下: mPlayer.setWebViewClient(new WebViewClient() { // autoplay when finished loading via javascript injection public void onPag

  • 详解Android App中使用VideoView来实现视频播放的方法

    通过VideoView播放视频的步骤: 1.在界面布局文件中定义VideoView组件,或在程序中创建VideoView组件 2.调用VideoView的如下两个方法来加载指定的视频 (1)setVidePath(String path):加载path文件代表的视频 (2)setVideoURI(Uri uri):加载uri所对应的视频 3.调用VideoView的start().stop().psuse()方法来控制视频的播放 VideoView通过与MediaController类结合使用,

  • Android自定义播放器控件VideoView

    介绍 最近要使用播放器做一个简单的视频播放功能,开始学习VideoView,在横竖屏切换的时候碰到了点麻烦,不过在查阅资料后总算是解决了.在写VideoView播放视频时候定义控制的代码全写在Actvity里了,写完一看我靠代码好乱,于是就写了个自定义的播放器控件,支持指定大小,可以横竖屏切换,手动左右滑动快进快退.好了,下面开始. 效果图有点卡,我也不知道为啥..... VideoView介绍 这个是我们实现视频播放最主要的控件,详细的介绍大家百度就去看,这里介绍几个常用的方法. 用于播放视频

  • Android编程使WebView支持HTML5 Video全屏播放的解决方法

    本文实例讲述了Android编程使WebView支持HTML5 Video全屏播放的解决方法.分享给大家供大家参考,具体如下: 1)需要在AndroidManifest.xml文件中声明需要使用HardwareAccelerate, 可以细化到Activity级别,如果不需要的View可以声明不要用加速,但是需要在代码中做,具体如下: a. 如果要声明整个应用都要加速: 复制代码 代码如下: <application ... android:hardwareAccelerated ="tr

  • Android如何让WebView中的HTML5页面实现视频全屏播放

    前言 本文主要是将最近工作中遇到的一个问题进行总结分享,主要介绍的是如何让WebView中H5页面全屏播放视频.关于这个问题,做一下简单分析,希望对大家有所帮助,下面话不多说了,来看看详细的介绍吧. 效果图 运行效果 其实很简单,就是配置问题.关键地方配好了,基本没什么问题了. 硬件加速 设置WebView 在清单需要配置的AndroidManifest.xml <application android:allowBackup="true" android:icon="

  • Android使用WebView播放flash的方法

    本文实例讲述了Android使用WebView播放flash及判断是否安装flash插件的方法.分享给大家供大家参考.具体实现方法如下: 一.问题: 最近帮一个同学做一个项目,断断续续的一些知识点记录一下.一个页面中有一个WebView,用来播放swf,如果系统中未安装flash插件,必须提示用户到market中安装. 二.解决方法: 下面做一个demo,效果图如下: 图1: 图2: 图3: 首先布局文件,很简单: 复制代码 代码如下: <RelativeLayout xmlns:android

  • Android编程实现WebView全屏播放的方法(附源码)

    本文实例讲述了Android编程实现WebView全屏播放的方法.分享给大家供大家参考,具体如下: 最近因为项目要用webview加载html5的视频,开始不能全屏播,做了很久才做出来!那按我的理解说下怎么实现全屏吧. 首先写布局文件activity_main.xml: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.

  • android使用videoview播放视频

    复制代码 代码如下: public class Activity01 extends Activity{ /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState); setContentView(R.layout.main); final VideoView vid

  • 一步步实现自定义View之播放暂停控件

    最近开始深入学习自定义View,通过模仿学习,再配合Kotlin,写了一些自定义控件,这次介绍的是类似于音乐播放暂停的一个控件 首先看一下效果图: 下面先分析一下原理: 状态1是播放状态,有两个小矩形,外面是一个圆,它需要最终变换成状态3的暂停状态 状态2是两个小矩形变成如图的黑色三角的一个过程 我们可以通过动画来实现它,两个小矩形分别变成三角形的一半 同时再给画布一个90度的旋转 具体实现: 1.继承View class PlayPauseView : View 2.重写构造函数 constr

  • Android自定义view实现滚动选择控件详解

    目录 前言 需求 编写代码 主要问题 前言 上篇文章通过一个有header和footer的滚动控件(Viewgroup)学了下MeasureSpec.onMeasure以及onLayout,接下来就用一个滚动选择的控件(View)来学一下onDraw的使用,并且了解下在XML自定义控件参数. 需求 这里就是一个滚动选择文字的控件,还是挺常见的,之前用别人的,现在选择手撕一个,核心思想如下: 1.有三层不同大小及透明度的选项,选中项放在中间 2.接受一个列表的数据,静态时显示三个值,滚动时显示四个

  • Android自定义View简易折线图控件(二)

    继续练习自定义View,这次带来的是简易折线图,支持坐标点点击监听,效果如下: 画坐标轴.画刻度.画点.连线..x.y轴的数据范围是写死的 1 <= x <= 7 ,1 <= y <= 70 ..写活的话涉及到坐标轴刻度的动态计算.坐标点的坐标修改,想想就头大,这里只练习自定义View. 1.在res/values文件夹下新建attrs.xml文件,编写自定义属性: <?xml version="1.0" encoding="utf-8"

  • Android自定义View圆形进度条控件(三)

    继续练习自定义View,这次带来的圆形进度条控件与之前的圆形百分比控件大同小异,这次涉及到了渐变渲染以及画布旋转等知识点,效果如下: 虽然步骤类似,但是我还是要写,毕竟基础的东西就是要多练 1.在res/values文件夹下新建attrs.xml文件,编写自定义属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="Circ

  • Android利用Paint自定义View实现进度条控件方法示例

    前言 View的三大流程:测量,布局,绘制,自定义View学的是啥?无非就两种:绘制文字和绘制图像. 我们在上一篇文章<Android绘图之Paint的使用>中学习了Paint的基本用法,但是具体的应用我们还没有实践过.从标题中可知,本文是带领读者使用Paint,自定义一个进度条控件. 效果图 上图就是本文要实现的效果图. 实现过程 既然是自定义控件,本文的该控件是直接继承View,然后重写View的onMeasure和onDraw方法来实现.其中onMeasure主要作用是测量控件的宽/高.

  • Android重写View实现全新的控件

    通常情况下,Android实现自定义控件无非三种方式. Ⅰ.继承现有控件,对其控件的功能进行拓展. Ⅱ.将现有控件进行组合,实现功能更加强大控件. Ⅲ.重写View实现全新的控件 本文来讨论最难的一种自定义控件形式,重写View来实现全新的控件. 首先,我们要明白在什么样的情况下,需要重写View来实现一种全新的控件,一般当我们遇到了原生控件无法满足我们现有的需求的时候,我们此时就可以考虑创建一个全新的View来实现我们所需要的功能.创建一个全新View实现自定义控件,无非分成这么几步: Ⅰ.在

  • Android开发自定义双向SeekBar拖动条控件

    目录 目标:双向拖动的自定义View 实现步骤 自定义属性获取 确定自定义view尺寸 绘制相关的内容部分 滑动事件处理 目标:双向拖动的自定义View 国际惯例先预览后实现 我们要实现的就是一个段位样式的拖动条,用来做筛选条件用的,细心的朋友可能会发现微信设置里面有个一个通用字体的设置,拖动然后改变字体大小; 这个相对比微信那个的自定义view算是一个扩展,因为我们是双向滑动,这个多考虑的一点就是手指拖动的是哪一个滑动块! 我们先看下GIF预览,然后我们今天就一步步实现这个小玩意… 实现步骤

  • Android自定义星星可滑动评分控件

    本文实例为大家分享了Android自定义星星可滑动评分控件的具体方法,供大家参考,具体内容如下 此控件通过线性布局结合ImageView来实现. 具有展示分数,滑动评分功能,可设置0-10分,自行设置星星图片,是否可点击与滑动,星星间距. 效果如下: 需准备好下面三张图片 先看自定义属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name

  • ASP.NET自定义Web服务器控件之Button控件

    本文实例讲述了ASP.NET自定义Web服务器控件之Button控件实现方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: using System;  using System.Collections.Generic;  using System.ComponentModel;  using System.Linq;  using System.Text;  using System.Web;  using System.Web.UI;  using System.Web.U

随机推荐