Android SVG 自定义视频播放按钮

Header

        先来看一下效果如何

        很多人都看到过类似这样的视频播放按钮,那么,这样的按钮是如何实现的呢?其实也就两个知识点,一个是自定义控件,一个是SVG矢量动画,那么下面就来讲讲如何实现这个控件。

Body

1.SVG

何为SVG,可缩放矢量图形(Scalable Vector Graphics)。Android在5.0版本之后添加了对SVG矢量图形的支持,被越来越多的用到了Android的各个场景之中,不同于普通的位图,矢量图形是不会因为拉伸等情况而失去其原有的样貌,这也在适配方面提供了不少便利。

vector标签

在Android 5.0之后,我们可以通过VectorDrawable来实现矢量图形,图像体积小,而且缩放不失真。另外,可以通过使用

AnimatedVectorDrawable和属性动画来做矢量动画。

来看下vector标签的几个属性

1.name :指定矢量图形的名称

2.width :指定矢量图形的默认宽度,一般使用dp数值,如果在layout布局文件中将ImageView的layout_width设置为wrap_content,同时src设置为该矢量图形的话,则ImageView的宽度就是此处width的宽度

3.height :指定矢量图形的默认高度,效果和width的效果一样

4.viewportHeight :指定视图空间的高度,算是虚拟坐标系的高度,后续坐标系的信息都基于此数据

5.viewportWidth :指定视图空间的宽度,算是虚拟坐标系的宽度,效果和第4条一样

6.alpha :指定图像的透明度(0——1)

这是几个比较主要的属性。那么,再来说一下width和viewportWidth的区别是什么?width指的是显示在屏幕上可以直观看到的宽度数据,而viewportWidth呢,其实就是将宽度变为坐标系,将宽度等分为viewportWidth的数值,图像中所有的路径以坐标的形式进行定位,这样不论矢量图形的宽高如何设置,其图形内部的路径都是等比例缩放的,这样不论图形实际大小,图像都不会被拉伸,不会失真。

group标签

group标签可以包裹多个path,可以将这些path捆绑起来,来执行一些共同的行为

来看下group的几个属性

1.name :指定group的名称

2.pivotY : 指定旋转动画的轴Y坐标

3.pivotX : 指定旋转动画的轴X坐标

4.rotation : 指定group的旋转角度

5.scaleX : 指定group在横向上的缩放比例,值为0.5则表示缩小一半,值为2则表示放大一倍

6.scaleY : 指定group在纵向上的缩放比例,值为0.5则表示缩小一半,值为2则表示放大一倍

7.translateX : 指定group在横向上的平移距离

8.translateY : 指定group在纵向上的平移距离

path标签

path标签则是描述图形的路径,通过路径的各个动作执行就可以画出图形的具体图像

1.name : 指定path路径的名称

2.pathData : path的具体内容

3.fillColor : 路径所包围的内容的内部颜色,如果不设置,则只绘制轮廓边线,而不填充内部

3.trimPathEnd : 表示整段路径的显示结束位置(0——1)如数值为0.5,则显示路径从头开始到一半。

4.trimPathStart : 表示整段路径的显示开始位置(0——1)如数值为0.5,则显示从路径一半开始到结束位置

如若trimPathStart和trimPathEnd都设置了,则显示start起始到end结束

5.strokeColor :路径边框颜色,如果不设置,则不绘制轮廓边线

6.strokeWidth : 路径轮廓边线宽度

7.strokeLineCap : 指定曲线的首尾外观。取值说明有三个:butt(默认值,直线边缘)、round(圆形边缘)、square(方形边缘)

8.trimPathoffset:指定几何路径的绘制偏移。取值为0.0到1.0,表示线条从trimPathOffset+trimPathStart处一直绘制到trimPathOffset+trimPathEnd处

以上为几条比较重要的属性

路径

那么矢量图象的具体绘制路径在哪里定义呢?就是path标签下的pathData属性。其值要符合SVG的标准,那么就说下SVG标准的几个语法

命令参数说明
Mx,y将画笔移动到指定位置x,y
Lx,y从当前点画一条直线到x,y
Hx从当前点画水平线到指定x位置
Vy从当前点画垂直线到指定y位置
Cx1,y1,x2,y2,endX,endY三次贝塞尔曲线(当前位置到endX,endY,然后中间部分分别向x1,y1和x2,y2方向突出)
Sx2,y2,endX,endY三次贝塞尔曲线
Qx,y,endX,endY二次贝塞尔曲线(当前位置到endX,endY,然后中间部分曲线向x,y方向突出)
ArX,rY,xRotation,flag1,flag2,x,y弧线(rX 横轴半径,rY 纵轴半径,xRotation 圆弧旋转角度,也就是圆弧的X轴和水平方向顺时针方向的夹角,可以想象成。一个水平的椭圆饶中心点顺时针旋转XROTATION 的角度,flag1    1表示大角度弧度,0表示小角度弧度,flag2    顺时针1,逆时针0,x,y为终点坐标)
TendX,endY映射前面路径的重点
Z关闭路径(将初始位置和结束位置连接起来,形成一个闭合路径)

使用上面的指令时,需要注意的几点:

  • 坐标轴以(0,0)位中心,X轴水平向右,Y轴水平向下。
  • 所有指令大小写均可,大写绝对定位,参照全局坐标系,小写相对定位,参照父容器坐标系。
  • 指令和数据间的空格可以无视。
  • 同一指令出现多次可以用一个。    

大概了解了这些,就基本可以绘制一些简单的矢量图形,那么下面我们就来进行开头图形的绘制。

1.绘制一个三角形播放图形,绘制一个暂停图形

播放按钮大概就是这么个样子,那么我们就来看看使用SVG标准命令来绘制吧

首先,画笔初始位置在(0,0)位置,要先把画笔移动到(3,2)的位置,然后画一条直线到(7,5),再从(7,5)的位置画一条直线到(3,8)的位置,最后闭合路径就可以得到这个三角形图像。

play.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="100dp"
    android:height="100dp"
    android:viewportWidth="10.0"
    android:viewportHeight="10.0">

    <group
        android:name="playgroup"
        android:pivotX="5"
        android:pivotY="5">
        <path
            android:name="play"
            android:fillColor="#ffffff"
            android:pathData="M 3.0,2.0 L 7.0,5.0 L 3.0,8.0 Z"/>
    </group>

</vector>

具体的xml内容如上,这样就可以画出一个三角形图像,看下预览效果

但是,这样是不行的,因为后面我们需要把这个三角形变为两个矩形,所以这个三角形也要最好是两个部分,所以需要这样绘制

这样的话,就先将画笔移动到(3,2)位置,画直线到(7,5)位置,再画直线到(3,5)位置,再画回起始位置(3,2),移动画笔到(3,8)位置,画直线到(7,5)位置,再画直线到(3,5)位置,再到(3,8)位置,这样就完成了两个三角形。

play.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="100dp"
    android:height="100dp"
    android:viewportWidth="10.0"
    android:viewportHeight="10.0">

    <group
        android:name="playgroup"
        android:pivotX="5"
        android:pivotY="5">
        <path
            android:name="play"
            android:fillColor="#ffffff"
            android:pathData="M 3.0,2.0 L 3.0,2.0 L 7.0,5.0 L 3.0,5.0 L 3.0,2.0 M 3.0,8.0 L 3.0,8.0 L 7.0,5.0 L 3.0,5.0 L 3.0,8.0"/>
    </group>


</vector>

效果和上面的是一样的。

接下来就是绘制暂停按钮

有人看为什么暂停键是两条横着的矩形,大家看Header里的效果,起始暂停初始的时候是横着的,不过是做了一个旋转动画,所以在绘制初始图片的时候,需要横向绘制。这个就不多说了,直接上代码

pause.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="100dp"
    android:height="100dp"
    android:viewportWidth="10.0"
    android:viewportHeight="10.0">

    <group
        android:name="pausegroup"
        android:pivotX="5"
        android:pivotY="5">
        <path
            android:name="pause"
            android:fillColor="#ffffff"
            android:pathData="M 2.0,2.0 L 8.0,2.0 L 8.0,4.0 L 2.0,4.0 L 2.0,2.0 M 2.0,8.0 L 8.0,8.0 L 8.0,6.0 L 2.0,6.0 L 2.0,8.0"/>
    </group>


</vector>

好,到这儿,我们需要的两个矢量图形就准备好了,接下来要做的就是编写动画,让两个图形可以进行变换。

2. objectAnimator(属性动画)

首先我们看play变换为pause,如何形变

animator_play_to_pause

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:propertyName="pathData"
    android:valueType="pathType"
    android:valueFrom="M 3.0,2.0 L 3.0,2.0 L 7.0,5.0 L 3.0,5.0 L 3.0,2.0 M 3.0,8.0 L 3.0,8.0 L 7.0,5.0 L 3.0,5.0 L 3.0,8.0"
    android:valueTo="M 2.0,2.0 L 8.0,2.0 L 8.0,4.0 L 2.0,4.0 L 2.0,2.0 M 2.0,8.0 L 8.0,8.0 L 8.0,6.0 L 2.0,6.0 L 2.0,8.0">

</objectAnimator>

来看下objectAnimator的这几个属性:

1.duration :表示该动画执行时长

2.propertyName : 表示该动画的目标属性

3.valueType :表示该动画的变化数值类型(pathType   colorType    intType    floatType)

4.valueFrom :表示起始数值

5.valueTo :表示终止数值

该objectAnimator所表示的动画就是,在300毫秒内,将valueFrom的路径变化为valueTo的路径。也就可以把两个三角形变为两个矩形。

这是从play变为pause,当然还有pause变为play,代码基本一样,只是需要把valueFrom和valueTo的数据对调位置就可以了,这里就不贴代码了。

从效果图中我们看到,除了形变以外,还有一个旋转动画,那么接下来就定义一下旋转动画

animator_rotation_play_to_pause.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="450"
    android:propertyName="rotation"
    android:valueType="floatType"
    android:valueFrom="0"
    android:valueTo="-90">

</objectAnimator>

当然,还有反方向的旋转,道理一样,将valueFrom和valueTo对调位置

3. animated-vector

    我们有了矢量图形,也有了动画,那如何将这两者结合起来呢?就用到了animated-vector标签,起始就是AnimatedVectorDrawable。从play转为pause,首先要把三角转变为矩形,并且逆时针旋转90度,直接上代码

animated_vector_play_to_pause.xml

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    tools:ignore="NewApi"
    android:drawable="@drawable/play">

    <target
        android:animation="@animator/animator_play_to_pause"
        android:name="play"/>

    <target
        android:animation="@animator/animator_rotatation_play_to_pause"
        android:name="playgroup"/>

</animated-vector>

来看下animated-vector的属性:

1.drawable : 表示要进行操作的图像

2.target : 1.animation : 表示要执行的动画  2.name : 表示要对哪一部分进行动画(name的数值要和vector中的path或者group的名称要对应,否则会找不到目标图像)

上面表示的就是 要对play所对应的path进行路径形变,然后要对playgroup所对应的组进行旋转操作。

当然,还有pause转play的动画

animated_vector_pause_to_play.xml

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:drawable="@drawable/pause">
    <target
        android:animation="@animator/animator_pause_to_play"
        android:name="play"/>

    <target
        android:animation="@animator/animator_rotation_pause_to_play"
        android:name="playgroup"/>
</animated-vector>

4.animated-selector

    由于,我们的控件是有两种图像,两种动画的执行,两种存在的状态,所以这里我们需要animated-selector,动画选择器来把两个状态的图像和动画联系起来,并通过操作控件的某个属性来将两种状态进行切换。

<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/play"
        android:drawable="@drawable/play"
        android:state_checked="false"/>

    <item
        android:id="@+id/pause"
        android:drawable="@drawable/pause"
        android:state_checked="true"/>

    <transition
        android:fromId="@+id/play"
        android:toId="@+id/pause"
        android:drawable="@drawable/animated_vector_play_to_pause"/>

    <transition
        android:fromId="@+id/pause"
        android:toId="@+id/play"
        android:drawable="@drawable/animated_vector_pause_to_play"/>
</animated-selector>

上面的属性我就不说了,网上一大把,看下基本就知道啥意思了。这样适量动画这一块的工作就算结束了。

自定义控件

自定义控件,具体的我就不在这里阐述了,可以点击这里去看我的另一篇文章,里面有关于自定义控件的东西,网上也是一大把

现在这里呢,也不是完全的自定义控件,而是对原有的控件进行一个改造,对imageview进行改造。

1.创建一个view并且继承imageview,用来承载我们刚才做好的矢量图形

public class PlayButton extends AppCompatImageView {
    
    public PlayButton(Context context) {
        super(context);
    }

    public PlayButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PlayButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

定义一个boolean成员变量记录当前状态,定义一个监听提供给使用者方便在不同状态下进行其他操作,创建play和pause方法,不仅可以通过点击本身切换状体,也可以通过调用api来进行状态改变。(还要防止快速点击的问题),我就直接上代码吧,没啥好说的

public class PlayButton extends AppCompatImageView {

    //当前播放状态 true正在播放  false暂停播放
    private boolean isPlay = false;

    //提供给使用者的点击监听
    private OnPlayOrPauseClick onPlayOrPauseClick;

    public PlayButton(Context context) {
        super(context);
    }

    public PlayButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public PlayButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private void initView() {
        //初始化图像,将选择器配置进去
        setImageResource(R.drawable.animated_selector_play_and_pause);
        //设置点击事件
        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                
                changeState();
                
            }
        });
    }
    
    //根据当前状态进行切换
    private void changeState(){
        if (!isFastClick()) {
            isPlay = !isPlay;
            final int[] state = {android.R.attr.state_checked * (isPlay ? 1 : -1)};
            setImageState(state, true);
            if (null != onPlayOrPauseClick) {
                onPlayOrPauseClick.onClick(!isPlay);
            }
        }
    }
    
    //播放
    public void toPlay(){
        if (isPlay){
            return;
        }else{
            changeState();
        }
    }
    
    //暂停
    public void toPause(){
        if (!isPlay){
            return;
        }else{
            changeState();
        }
    }

    private long lastClickTime;

    //防止快速点击
    public boolean isFastClick() {
        boolean flag = true;
        long currentClickTime = System.currentTimeMillis();
        if ((currentClickTime - lastClickTime) >= 500) {
            flag = false;
        }
        lastClickTime = currentClickTime;
        return flag;
    }

    //获取当前播放状态
    public boolean isPlay() {
        return isPlay;
    }
    
    

    public void setOnPlayOrPauseClick(OnPlayOrPauseClick onPlayOrPauseClick) {
        this.onPlayOrPauseClick = onPlayOrPauseClick;
    }

    public interface OnPlayOrPauseClick {
        void onClick(boolean isPlay);
    }
}

自此,控件就完成了,来,测一下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context="com.will.svg.MainActivity">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginBottom="20dp">
        <TextView
            android:id="@+id/play"
            android:layout_width="90dp"
            android:layout_height="90dp"
            android:background="#999999"
            android:text="play"
            android:gravity="center"
            android:layout_marginRight="20dp"/>
        <TextView
            android:id="@+id/pause"
            android:layout_width="90dp"
            android:layout_height="90dp"
            android:text="pause"
            android:gravity="center"
            android:background="#999999"/>
    </LinearLayout>


    <com.will.svg.PlayButton
        android:id="@+id/play_image_button"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="#999999"/>

</LinearLayout>

 

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final PlayButton button = findViewById(R.id.play_image_button);
        button.setOnPlayOrPauseClick(new PlayButton.OnPlayOrPauseClick() {
            @Override
            public void onClick(boolean isPlay) {
                if (isPlay){
                    Toast.makeText(MainActivity.this,"要关闭喽",Toast.LENGTH_SHORT).show();
                }else{
                    Toast.makeText(MainActivity.this,"要打开喽",Toast.LENGTH_SHORT).show();
                }
            }
        });

        TextView play = findViewById(R.id.play);
        TextView pause = findViewById(R.id.pause);
        play.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                button.toPlay();
            }
        });

        pause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                button.toPause();
            }
        });
    }
}

大功告成,效果就是Header中gif的效果。

End

好了,如果能把这个实现出来,也算是对SVG有那么一点皮毛的认识了,后面会有更深入的探索吧

Demo地址:https://github.com/ApeForProgram/SVGPlayButton

仓库地址:https://github.com/ApeForProgram/SVGPlayButton.git

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android SVG (Scalable Vector Graphics) 是一种用于在 应用程序中显示可缩放矢量图形的格式。SVG 图像使用 XML 语法进行描述,可以无损地缩放和放大,而不会失去清晰度和质量。 Android 提供了一些方法来在应用程序中加载和显示 SVG 图像。最常用的方法是使用第三方库,例如 AndroidSVGSvg-androidAndroidSVG Library。这些库提供了用于解析和渲染 SVG 图像的类和方法。 要在 Android 应用程序中使用 SVG 图像,首先需要将 SVG 文件保存在项目的资源文件夹中。然后,可以使用相应的库来加载和显示 SVG 图像。通常,需要将 SVG 图像转换为 Android 可以理解和显示的格式,例如 Bitmap 或 Drawable。 以下是一个简单的示例代码,演示如何使用 AndroidSVG 库加载和显示 SVG 图像: ```java import com.caverock.androidsvg.SVG; import com.caverock.androidsvg.SVGParseException; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Picture; import android.graphics.drawable.PictureDrawable; import android.widget.ImageView; public void loadSvgImage(Context context, ImageView imageView, int svgResId) { try { SVG svg = SVG.getFromResource(context, svgResId); Picture picture = svg.renderToPicture(); PictureDrawable drawable = new PictureDrawable(picture); imageView.setImageDrawable(drawable); } catch (SVGParseException e) { e.printStackTrace(); } } ``` 在上面的示例中,`loadSvgImage` 方法接收一个上下文对象、一个 ImageView 和一个指向 SVG 文件的资源 ID。它使用 AndroidSVG 库从资源中加载 SVG 图像,并将其渲染为 PictureDrawable,然后将其设置为 ImageView 的图像。 这只是一个基本示例,你可以根据自己的需求进行更多的定制和扩展。希望这能帮助你开始在 Android 应用程序中使用 SVG 图像!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值