- 给 path 定义 name,如下所示
- 定义一个 Animator 文件,以表示对这幅 Vector 图像做动画
<objectAnimator xmlns:android=“http://schemas.android.com/apk/res/android”
android:propertyName=“trimPathStart”
android:valueFrom=“1”
android:valueTo=“0”
android:duration=“3000”
需要注意的是,这里的文件是对应 Vector 中 path 标签的,这里动画效果是动态改变 path 标签的 trimPathStart 属性值,从 0 ~ 1 。
- 定义 animated-vector 进行关联
在上述代码中,drawable 代表关联的 vector 图像,target 代表将 path name 和动画进行关联
- 代码中进行设置
class SVGDemo1Activity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_svg)
startAnimatabe()
}
private fun startAnimatabe() {
val animatedVectorDrawable = AnimatedVectorDrawableCompat.create(this, R.drawable.line_animated_vector)
iv.setImageDrawable(animatedVectorDrawable)
val animatable = iv.drawable as Animatable
animatable.start()
}
}
实战
输入搜索动画
- 利用在线绘制 SVG 图标网站 制作搜索图标
可以自己随意捣鼓绘制,绘制好了之后点击视图->源代码,将 SVG 代码复制出来保存成 search_svg.xml
点击空白或者直接将 SVG 拖拽指定区域进行转换
- 将转换好的 Android 格式的 vector 导入 AS
- 开始制作动画关联
//1.在 /res/aniamator 文件夹下 定义动画
<?xml version="1.0" encoding="utf-8"?><objectAnimator xmlns:android=“http://schemas.android.com/apk/res/android”
android:propertyName=“trimPathStart”
android:valueFrom=“1”
android:valueTo=“0”
android:duration=“2000”
//2. 在/res/drawable/ 定义 vector
<?xml version="1.0" encoding="utf-8"?>
//3. 在/res/drawable/ 关联动画和 vector
<?xml version="1.0" encoding="utf-8"?>
- 效果
警车灯闪烁
今日头条下拉刷新动画
来一个复杂组合动画,请看下面效果图:
- 准备 vector 数据
- 定义顺时针执行动画并做 pathData 变换
这里拿其中一个位置变化来举例说明:
<?xml version="1.0" encoding="utf-8"?><set xmlns:android=“http://schemas.android.com/apk/res/android”
android:ordering=“sequentially”>//按顺序执行
//依次执行 pathData 位置变换
<objectAnimator
android:duration=“600”
android:interpolator=“@android:interpolator/decelerate_cubic”
android:propertyName=“pathData”
android:valueFrom="
M100,30
L180,30
M180,30
L180,90
M180,90
L100,90
M100,90
L100,30"
android:valueTo="
M100,120
L180,120
M180,120
L180,180
M180,180
L100,180
M100,180
L100,120"
android:valueType=“pathType” />
<objectAnimator
android:duration=“600”
android:interpolator=“@android:interpolator/decelerate_cubic”
android:propertyName=“pathData”
android:valueFrom="
M100,120
L180,120
M180,120
L180,180
M180,180
L100,180
M100,180
L100,120"
android:valueTo="
M20,120
L100,120
M100,120
L100,180
M100,180
L20,180
M20,180
L20,120"
android:valueType=“pathType” />
<objectAnimator
android:duration=“600”
android:interpolator=“@android:interpolator/decelerate_cubic”
android:propertyName=“pathData”
android:valueFrom="
M20,120
L100,120
M100,120
L100,180
M100,180
L20,180
M20,180
L20,120"
android:valueTo="
M20,30
L100,30
M100,30
L100,90
M100,90
L20,90
M20,90
L20,30"
android:valueType=“pathType” />
如果对标签中的定义还不了解的先去看下文章中 path 标签
中的说明。如果不理解标签意思,根本就看不懂。
- 进行关联
<animated-vector xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:drawable=“@drawable/ic_toutiao”
tools:targetApi=“lollipop”>
- 代码控制重复执行
class SVGDemo1Activity : AppCompatActivity() {
var reStartTT = @SuppressLint(“HandlerLeak”)
object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
startAnimatabe(R.drawable.line_animated_toutiao, true)
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_svg)
//水滴动画
startWaterDropAnimator.setOnClickListener {
startAnimatabe(R.drawable.line_animated_vector, false)
}
//搜索动画
startSearchAnimator.setOnClickListener {
startAnimatabe(R.drawable.line_animated_search, false)
}
//执行警车动画
startPoliceCarAnimator.setOnClickListener {
startAnimatabe(R.drawable.line_animated_car, false)
}
//执行头条动画
startTTAnimator.setOnClickListener {
startAnimatabe(R.drawable.line_animated_toutiao, true)
}
}
private fun startAnimatabe(lineAnimatedVector: Int, isRegister: Boolean): Animatable {
val animatedVectorDrawable = AnimatedVectorDrawableCompat.create(this, lineAnimatedVector)
iv.setImageDrawable(animatedVectorDrawable)
val animatable = iv.drawable as Animatable
animatable.start()
animatedVectorDrawable!!.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
super.onAnimationEnd(drawable)
if (!isRegister) return
animatedVectorDrawable.unregisterAnimationCallback(this)
//重新开始在 xml 设置 restart 无效暂时用 Handler 实现了。
reStartTT.sendEmptyMessage(0)
}
})
return animatable
}
}
绘制中国地图
该篇之前实现 SVG pathData 都是利用 ImageView 来实现,并不是所有的场合都适合上面的方式,比如我想要实现 pathData 区域点击,那么上面所讲的方式应该是不能实现,下面我们以一个实例来看怎么自定义 View 实现 PathData 和 pathData 区域点击事件。
下面我们利用 path 来绘制一个中国地图,先来看一个最终效果图,如下:
看起来是不是很炫,还不错,嘿嘿,下面我们就来看一下如何实现。
- 准备地图 SVG
-
首先去下载地图数据
-
选择下载免费的地图数据
- 找到对应的国家点击下载 svg 数据
- 选择对应的地图数据,我这里下载的是高质量的 SVG
- SVG to Vector xml
将下载好的 china.svg 格式的文件转为 vector 节点的 xml 数据 或者用 AS 自带转也行,看个人爱好。
转好之后放入 AS 中,如下所示
现在有了这些数据,我们就可以解析 xml path
节点,拿到 pathData
数据我们不就可以绘制 path 了嘛。下面就开始解析 xml ,解析的方法很多种,我们这里用 dom 解析。
- 开始解析 xml
解析 xml 有很多种方式,这里就直接使用 DOM
解析,pathData2Path 我这里直接用 Android SDK 提供的 android.support.v4.graphics#PathParser
由于源码中它被标注了 hide
属性 ,我们需要直接将它 copy 到我们自己项目中, 具体转化请看如下代码:
/**
- 开始解析 xml
*/
public fun dom2xml(stream: InputStream?): MutableList {
mapDataLists.clear()
//dom
val newInstance = DocumentBuilderFactory.newInstance()
val newDocumentBuilder = newInstance.newDocumentBuilder()
//拿到 Docment 对象
val document = newDocumentBuilder.parse(stream)
//获取 xml 中属于 path 节点的所有信息
val elementsByTagName = document.getElementsByTagName(PATH_TAG)
//定义四个点,确定整个 map 的范围
var left = -1f
var right = -1f
var top = -1f
var bottom = -1f
//开始遍历标签,拿到 path 数据组
for (pathData in 0 until elementsByTagName.length) {
val item = elementsByTagName.item(pathData) as Element
val name = item.getAttribute(“android:name”)
val fillColor = item.getAttribute(“android:fillColor”)
val strokeColor = item.getAttribute(“android:strokeColor”)
val strokeWidth = item.getAttribute(“android:strokeWidth”)
val pathData = item.getAttribute(“android:pathData”)
val path = PathParser.createPathFromPathData(pathData)
mapDataLists.add(MapData(name, fillColor, strokeColor, strokeWidth, path))
//获取控件的宽高
val rect = RectF()
//获取到每个省份的边界
path.computeBounds(rect, true)
//遍历取出每个path中的left取所有的最小值
left = if (left == -1f) rect.left else Math.min(left, rect.left)
//遍历取出每个path中的right取所有的最大值
right = if (right == -1f) rect.right else Math.max(right, rect.right)
//遍历取出每个path中的top取所有的最小值
top = if (top == -1f) rect.top else Math.min(top, rect.top)
//遍历取出每个path中的bottom取所有的最大值
bottom = if (bottom == -1f) rect.bottom else Math.max(bottom, rect.bottom)
}
//MAP 的矩形区域
MAP_RECTF = RectF(left, top, right, bottom)
return mapDataLists;
}
复制代码
- 进行控件测量适配横竖屏切换和宽高定义 wrap_content 模式
/**
- 开始测量
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//测量模式
var widthMode = MeasureSpec.getMode(widthMeasureSpec)
var heightMode = MeasureSpec.getMode(heightMeasureSpec)
//测量大小
widthSize = MeasureSpec.getSize(widthMeasureSpec)
heightSize = MeasureSpec.getSize(heightMeasureSpec)
if (!MAP_RECTF.isEmpty && mMapRectHeight != 0f && mMapRectWidth != 0f) {
//显示比例
scaleHeightValues = heightSize / mMapRectHeight
scaleWidthValues = widthSize / mMapRectWidth
}
//xml 文件中宽高 wrap_content
if (widthMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.AT_MOST) {
//如果是横屏宽保留最大,高需要适配
if (widthSize < heightSize && mMapRectHeight != 0f) {
setMeasuredDimension(widthSize, (mMapRectHeight * scaleWidthValues).toInt())
} else {
setMeasuredDimension(widthSize, heightSize)
}
} else {
setMeasuredDimension(widthSize, heightSize)
}
}
- 开始绘制 path
/**
- 绘制 Map 数据
*/
@SuppressLint(“Range”)
private fun drawMap(canvas: Canvas) {
canvas.save()
if (widthSize > heightSize) {
canvas.scale(scaleWidthValues, scaleHeightValues)
} else {
canvas.scale(scaleWidthValues, scaleWidthValues)
}
mapDataList.forEach { data ->
run {
if (data.isSelect) {
drawPath(data, canvas, Color.RED)
} else {
drawPath(data, canvas, Color.parseColor(data.fillColor))
}
}
}
canvas.restore()
canvas.drawText(“中国🇨🇳地图”, widthSize / 2 - mPaintTextTitle.measureText(“中国🇨🇳地图”) / 2f, 100f, mPaintTextTitle)
}
/**
- 开始绘制 Path
结尾
- 腾讯T4级别Android架构技术脑图;查漏补缺,体系化深入学习提升
- 一线互联网Android面试题含详解(初级到高级专题)
这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率
有Android开发3-5年基础,希望突破瓶颈,成为架构师的小伙伴,可以关注我
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
olor))
}
}
}
canvas.restore()
canvas.drawText(“中国🇨🇳地图”, widthSize / 2 - mPaintTextTitle.measureText(“中国🇨🇳地图”) / 2f, 100f, mPaintTextTitle)
}
/**
- 开始绘制 Path
结尾
- 腾讯T4级别Android架构技术脑图;查漏补缺,体系化深入学习提升
[外链图片转存中…(img-YQ6zfBUG-1714703478295)]
- 一线互联网Android面试题含详解(初级到高级专题)
这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率
[外链图片转存中…(img-CbTjiKaZ-1714703478295)]
有Android开发3-5年基础,希望突破瓶颈,成为架构师的小伙伴,可以关注我
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!