放荡不羁SVG讲解与实战之Android高级UI
温馨提示
请拖动到文章末尾,长按识别「抽奖」小程序。现金红包等你来拿。
作者:猛猛的小盆友
链接:
https://juejin.im/post/5ca9f65e6fb9a05e472b9cab
本文由作者授权发布。
五一很快就结束了,大家五一都去哪里玩了?马上就要回到工作岗位上,收拾好心情,认真的投入到一周忙碌的工作中。
目录
一、前言
二、SVG小课堂
三、简单使用
四、实战
五、写在最后
一、前言
SVG 在安卓5.0被引入,因为其放大后不会模糊的优秀表现,被使用也是越来越多。今天小盆友也来谈谈这个优秀的SVG,同时分享一些个人比较喜欢的知识小点。老规矩,先上实战图。
"手写"掘金
地图查阅器
二、SVG小课堂
1、SVG是什么
SVG 全称 Scalable Vector Graphics ,翻译一下即为 可缩放的矢量图形。
2、优点
SVG 的优点很多,而且在不同的场景优点也会有所不同,小盆友觉得 SVG 给我带来的优点如下几点
缩放均不失真,这带来的好处是我们不需要多套分辨率的图标;
文件相对较小,相较于 JPEG 和 GIF 格式的文件会小些;
以 XML 为结构,可修改也可扩展,用最简单的记事本也能进行相应的修改;
高交互性,这一点在实战时就能体现出来啦;
3、缺点
这个缺点,说的并不是SVG的缺点,而是在 Android 中使用SVG的缺点或局限。
(1) 动画兼容问题
前言中提到 SVG 是在5.0之后引入,虽然作为一个图标资源并不会有兼容问题。
但是如果对 SVG 进行使用动画时,则需要进行兼容性处理。否在 5.0 以下会闪退,毕竟 4.4 的占有率还 10.3%左右(如下图,图片来自 Android Studio 的统计)。
至于如何使用和兼容,我们在下一小节进行说明。
2、动画限制问题
动画限制这一点其实准确来说,不属于缺点,小盆友认为是不够灵活。
因为SVG的动画是通过属性动画进行执行的,我们知道属性动画最终是反射调用到类的 setXxx(Xxx就是我们设置的属性名称),所以如果该类没有对应的方法则是没有作用的。
对 “属性动画” 源码兴趣的童鞋可以移步小盆友的另一篇博文,带有活力的属性动画源码分析与实战。
接下来的一个问题就是,属性动画反射回调的类是哪个类呢?这里有两种情况,一种是针对 Group 标签,一种是针对 Path 标签。
但在说明具体具体类之前,我们有必要说明 Group 和 Path 标签的层级关系。
如下图所示,叶子节点只能为Path标签,而 Group标签用于装载Path标签或Group标签。值得一提的是 Vector 可以直接包含一个或多个Path, 而不一定需要包含Group。
接着我们来说说他们各自的具体反射类,Group标签 对应的是:
VectorDrawableCompat$VGroup // 类
其类的内部方法如下,带 set 开头的方法,已经用红框圈出,这代表着我们为Group标签设置的属性动画所作用的属性就只能局限于这几个方法中。
Path标签对应的是:
VectorDrawableCompat$VFullPath
继承于 VectorDrawableCompat$VPath,这两个类的内部方法如下,同样用红框圈出 set 开头的方法,所以我们通过属性动画对Path标签进行控制的只能这几个属性。
小结一下,这些方法能满足我们一些简单的动画,但是设计师来了一个较为骚气的交互,这时我们比较尴尬了,因为我们没法进行扩展,没法设置我们自己想要的动画逻辑。
三、简单使用
我们先来阐述如何将SVG常规使用起来。但在这之前我们需要说明一下 SVG 中绘制 Path 的语法。
1、绘制语法
path 的 pathData属性内装载的就是路径数据,其语法如下:
M = moveto(M X,Y) :将画笔移动到指定的坐标位置 L = lineto(L X,Y) :画直线到指定的坐标位置 H = horizontal lineto(H X):画水平线到指定的X坐标位置 V = vertical lineto(V Y):画垂直线到指定的Y坐标位置 C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三阶贝赛曲线 S = smooth curveto(S X2,Y2,ENDX,ENDY):三阶贝赛曲线 Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二阶贝赛曲线 T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射 A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线 Z = closepath():关闭路径
小盆友个人认为,这些语法作为一个了解即可,并不需要记忆,因为 SVG 的资源文件一般不需要我们程序猿自行绘制,只是偶尔需要修改一下,所以要求并不是很高。
现在有很多在线编辑SVG工具,可以通过绘制后,将路径数据拷贝下来稍作修改,便可使用。
“手写”掘金 的 SVG资源就是小盆友从掘金官网获取后,进行一些简单的修改,所以只需要了解,需要修改时会运用就行。
2、作为静态图片资源
在 Android 中的常使用的模版为:
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="xxdp" android:height="yydp" android:viewportWidth="xx" android:viewportHeight="yy"> <group> <path android:fillColor="#006CFF" android:pathData="xxxx" /> ....more path or group </group> ....more path or group
</vector>
在 vector 标签中的:
android:width 和 android:height
表示的是 SVG的大小,而:
android:viewportWidth 和 android:viewportHeight
表示的是将:
android:width 和 android:height
划分成多少个等份,随后的 Group 和 Path 的坐标则是基于这一比例进行编写。
group 和 path 我们在前面已经提过了,就不再赘述。
我们举个简单的例子,用 SVG画出 如下图形,并将其使用:
具体的SVG代码如下:
// ic_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="200dp" android:height="200dp" android:viewportWidth="100" android:viewportHeight="100"> <path android:name="top" android:pathData=" M 20,20 L 50,20 80,20" android:strokeWidth="5" android:strokeColor="#000000" android:strokeLineCap="round" /> <path android:name="middle" android:pathData=" M 20,50 L 50,50 80,50" android:strokeWidth="5" android:strokeColor="#000000" android:strokeLineCap="round" /> <path android:name="bottom" android:pathData=" M 20,80 L 50,80 80,80" android:strokeWidth="5" android:strokeColor="#000000" android:strokeLineCap="round" /> </vector>
使用其实和普通的图片资源一样,ic_menu资源 便是我们的 SVG 图形:
<ImageView android:layout_width="50dp" android:layout_height="50dp" android:layout_marginTop="10dp" android:src="@drawable/ic_menu" />
这里不存在兼容问题,小盆友在4.4的机子上也有测试过。
3、作为动态图片资源
SVG 的动画是比较有趣的,但我们在 “动画限制问题” 小节中提到,存在着兼容问题,5.0之前的版本不能使用SVG动画。
所以我们需要新建一个 drawable-anydpi-v21
文件夹,来存放我们的动画资源,具体存放结构和代码如下:
animated-vector
起着 扣接 SVG静态资源 和 属性动画 的作用。
// menu.xml
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/ic_menu"> <target android:name="top" android:animation="@animator/top_anim" /> <target android:name="bottom" android:animation="@animator/bottom_anim" /> </animated-vector> // top_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="500" android:interpolator="@android:interpolator/accelerate_decelerate" android:propertyName="pathData" android:valueFrom=" M 20,20 L 50,20 80,20" android:valueTo=" M 20,50 L 50,20 50,20" android:valueType="pathType" /> // bottom_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="500" android:interpolator="@android:interpolator/accelerate_decelerate" android:propertyName="pathData" android:valueFrom=" M 20,80 L 50,80 80,80" android:valueTo=" M 20,50 L 50,80 50,80" android:valueType="pathType" />
值得一提的是,这里的 pathData 最终就是调用了 VectorDrawableCompat$VPath 中的 setPathData,而参数类型便为 pathType。忘记的童鞋可以回 “动画限制问题” 小节查看下。
如果只是把我们这里使用的 menu资源放在 drawable-anydpi-v21
文件夹下,运行于 4.4的机子时,会报找不到相应资源的错误。
所以我们需要在 drawable
文件夹下,建一个相同名字的资源 menu资源,只是里面的内容不是 animated-vector
作为根标签,而是使用和 ic_menu资源 完全一样的内容。
最终在代码中进行兼容处理 5.0之后的版本开启动画,之前的版本切换图片资源:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { ((Animatable) img1.getDrawable()).start();
} else { img1.setImageDrawable( ContextCompat.getDrawable(SvgUseActivity.this, R.drawable.ic_back));
}
5.0之后版本的效果如下。5.0之前版本就只是简单图片切换,就不上图了:
四、实战
上一小节我们知道,对 SVG 添加动画,简单方便,但是也说明了使用系统自带的这一套操作无法实现较为复杂的交互,所以我们只能自己动手,才能丰衣足食了。
还记得小盆友在介绍优点时,说到SVG的格式是XML,这就是我们自己动手的切入点。因为格式为XML,所以可以自行解析,拿取其中的pathData数据转为Path路径,接下来就可以做很多有趣的事情。我们融入到实战中来体会这一趣事。
1、"手写"掘金
效果图
Github入口:
https://github.com/zincPower/UI2018
编码思路
(1)解析 SVG 文件首先需要将 “掘金”这一SVG进行XML解析,我们借助 DocumentBuilderFactory
类,为我们解析获取一棵DOM树。
// 从 XML文档 生成 DOM对象树
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document document = null;
try { document = factory.newDocumentBuilder().parse(inputStream);
} catch (SAXException | IOException | ParserConfigurationException e) { e.printStackTrace();
} finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); }
}
(2)获取并保存Path的数据在上一步中获取到DOM树之后,进行遍历DOM节点获取到 Path 数据,保存其填充的颜色和将 pathData 的数据翻译成 Path对象进行保存起来。
这里需要借助 PathParser
类将 pathData
的数据翻译成 Path对象 ,但是PathParser类 被打上了注解 @hide,我们无法直接使用,所以只能是将其拷贝一份放置我们的目录下来使用。具体核心代码如下:
// 遍历所有的 Path 节点
for (int i = 0; i < pathNodeList.getLength(); ++i) { Element pathNode = (Element) pathNodeList.item(i); // path 的 svg 路径 String pathData = pathNode.getAttribute(PATH_DATA); // path 的 颜色 String colorData = pathNode.getAttribute(FILL_COLOR); // 解析 path Path path = null; try { path = PathParser.createPathFromPathData(pathData); } catch (Exception e) { e.printStackTrace(); } // path 解析出错,退出 if (path == null) { mHandle.sendEmptyMessage(InnerHandler.ERROR); return; } int color = Color.parseColor(colorData); path.computeBounds(rect, true); left = left == -1 ? rect.left : Math.min(left, rect.left); right = right == -1 ? rect.right : Math.max(right, rect.right); top = top == -1 ? rect.top : Math.min(top, rect.top); bottom = bottom == -1 ? rect.bottom : Math.max(bottom, rect.bottom); PathData item = new PathData(); item.path = path; item.color = color; pathDataList.add(item);
}
(3)进行缩放根据 SVG图像大小 和 画布大小,进行偏移和缩放,让SVG图像大小合适且居中显示于画布中。核心代码如下:
float mScale = calculateScale(mSvgRect.width(), mSvgRect.height(), getWidth(), getHeight()); // 移至中心
mCanvasMatrix.preTranslate(getWidth() / 2, getHeight() / 2);
mCanvasMatrix.preTranslate(-mSvgRect.width() / 2, -mSvgRect.height() / 2); mCanvasMatrix.preScale( mScale, mScale, mSvgRect.width() / 2, mSvgRect.height() / 2); canvas.setMatrix(mCanvasMatrix);
(4)借助 PathMeasure 和 属性动画,让其进行勾勒后填充属性动画开启后,每次刷新都通过 PathMeasure 对当前需要勾勒的Path进行裁剪绘制,达到一步步勾勒的效果。核心代码如下:
PathData pathData = mPathDataList.get(index); mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(pathData.color);
mPaint.setStrokeWidth(mLineWidth / mScale); mPathMeasure.setPath(pathData.path, false);
mPathMeasure.getSegment(0, mPathMeasure.getLength() * process, mAnimPath, true);
canvas.drawPath(mAnimPath, mPaint);
PathMeasure的使用,可以查看小盆友的另一篇博文:PathMeasure的API讲解与实战
2、地图查阅器
效果图
Github入口:https://github.com/zincPower/UI2018
编码思路
(1)解析SVG数据与“手写”掘金的事例一样,第一步也是解析数据,通过 PathParser
类将svg的数据转为Path对象,而颜色填充则由我们设置的数组决定。
同时还要保存好svg图像的大小,具体核心代码如下:
// 用于记录整个 svg 的实际大小
float left = -1;
float top = -1;
float right = -1;
float bottom = -1; // 计算出 path 的 rect
RectF rect = new RectF(); // 遍历所有的 Path 节点
for (int i = 0; i < pathNodeList.getLength(); ++i) { Element pathNode = (Element) pathNodeList.item(i); // path 的 svg 路径 String pathData = pathNode.getAttribute(DATA); // path 的 title String title = pathNode.getAttribute(TITLE); // 省略一些代码 path.computeBounds(rect, true); left = left == -1 ? rect.left : Math.min(left, rect.left); right = right == -1 ? rect.right : Math.max(right, rect.right); top = top == -1 ? rect.top : Math.min(top, rect.top); bottom = bottom == -1 ? rect.bottom : Math.max(bottom, rect.bottom); ItemData itemData = new ItemData(path, ContextCompat.getColor(getContext(), mMapColor[i % colorSize]), title); mapDataList.add(itemData);
} mSvgRect.left = left;
mSvgRect.top = top;
mSvgRect.right = right;
mSvgRect.bottom = bottom;
(2)缩放地图至View中心根据画布的大小 和 svg的大小,将我们的画布进行偏移和缩放,使我们的地图大小合适且居中放置(这里借助了矩阵,但最终会将该矩阵作用于我们的画布)
// 移至画布中心
mCanvasMatrix.preTranslate(getWidth() / 2, getHeight() / 2); // 移外边
float lastLeftMargin = mLastRectF.left - mSvgRect.left;
float lastTopMargin = mLastRectF.top - mSvgRect.top;
mCanvasMatrix.preTranslate(-lastLeftMargin, -lastTopMargin); // 移至中心
mCanvasMatrix.preTranslate(-mLastRectF.width() / 2, -mLastRectF.height() / 2); // 进行缩放
if (!mLastRectF.isEmpty()) { mScale = calculateScale( mLastRectF.width(), mLastRectF.height(), getWidth(), getHeight());
}
mCanvasMatrix.preScale( mScale, mScale, lastLeftMargin + mLastRectF.width() / 2, lastTopMargin + mLastRectF.height() / 2);
(3)如何交互至此我们的地图就已经能正常显示了,但还需要交互。交互最主要的问题是我们如何知道选中的是哪块区域。具体通过一下代码进行判断,便可知道我们是否触碰了 该Path所包含的区域:
/** * 是否在触碰的范围内 * * @param item 地图的每个数据项 * @param x 触碰点的x轴 * @param y 触碰点的y轴 * @return true:在范围内;false:在范围外 */
private boolean isTouch(ItemData item, float x, float y) { item.path.computeBounds(mTouchRectF, true); mTouchRegion.setPath( item.path, new Region((int) mTouchRectF.left, (int) mTouchRectF.top, (int) mTouchRectF.right, (int) mTouchRectF.bottom) ); return mTouchRegion.contains((int) x, (int) y);
}
(4)剩余操作获得了点击的区域,如何进行动画的过渡就是计算逻辑问题了。小盆友这里就不再展开讲这块的逻辑。这里用一句话概括,就是通过比较 上一次选中的Path区域 和 这次选中的Path区域 进行 中心坐标偏移和缩放。
五、写在最后
SVG 也是一把利器,挥舞得当可以让自己的App展现出别人所想不到的交互效果,希望这篇文章能让你体会到不一样的SVG。如果你有所收获就给我一个赞❤️并关注我吧,如果发现有那些欠妥的地方,请留言区与我讨论,我们共同进步。
高级UI系列的Github地址:请进入:
https://github.com/zincPower/UI2018
如果喜欢的话给我一个star吧?
推荐阅读
Canvas中的裁剪师讲解与实战Android高级UI
Android控件人生第一站,小红书任意拖拽标签控件
长按识别小程序,参与抽奖
▼更多现金红包,请长按二维码▼
目前100000+人已关注加入我们
红包天天有,别忘了点个「在看」
放荡不羁SVG讲解与实战之Android高级UI相关推荐
- 你连《Android高级UI与FrameWork源码》都搞不懂学什么Android?还敢面试阿里P7!
Android高级UI与FrameWork源码 重要性? 这块知识是现今使用者最多的,我们称之为Android2013~2016年的技术,但是,即使是这样的技术,Android开发者也往往因为网上Co ...
- Android 高级UI解密 (三) :Canvas裁剪 与 二维、三维Camera几何变换(图层Layer原理)
Android的绘图机制是核心内容之一,无论是什么样的功能最终都是以图像的形式呈现给用户.因此掌握Android的绘图技巧,有助于Android理解层次的提高,在面对产品经理提出的idea时也更有底气 ...
- android高级UI之贝塞尔曲线<上>---基本概念、德卡斯特里奥算法
在上一次android高级UI之Canvas综合案例操练 - cexo - 博客园对于Android UI绘制中核心的Canvas进行了相关的学习,这块的学习也中断一年多了,既然主业是Android开 ...
- Android 高级UI解密 (四) :花式玩转贝塞尔曲线(波浪、轨迹变换动画)
讲解此UI系列必然少不了一个奇妙数学曲线-–贝塞尔曲线,它目前运用于App的范围是在太广了,最初的QQ气泡拖拽,到个人界面的波浪效果.Loading波浪效果,甚至于轨迹变化的动画都可以依赖贝塞尔曲线完 ...
- Android 高级UI解密 (二) :Paint滤镜 与 颜色过滤(矩阵变换)
若是曾经查看过系统UI的源码, 会发现其中使用了一些渲染效果,例如将图片加上黑白.怀旧的效果,生活中常用的逆天美颜相机,其中的原理就是使用了滤镜效果.颜色通道过滤.若还要深究其原理组成,便涉及到了高等 ...
- android炫酷动画代码,Android高级UI特效仿直播点赞动画效果
Android高级UI特效仿直播点赞动画效果 发布时间:2020-10-02 16:06:18 来源:脚本之家 阅读:117 作者:mrr 本文给大家分享高级UI特效仿直播点赞效果-一个优美炫酷的点赞 ...
- Android高级UI系列教程(二)
上期回顾 Android高级UI系列教程(一)_我想月薪过万的博客-CSDN博客https://blog.csdn.net/qq_41885673/article/details/121870917 ...
- android高级UI视频全套
android高级UI视频全套 http://nez.cc/NfhYoO
- android高级UI之PathMeasure<一>--Path测量基础(nextContour、getPosTan、getMatrix、getSegment)
前言: 在上一次android高级UI之贝塞尔曲线<下>--贝塞尔曲线运用:QQ消息气泡完成了对于贝塞尔曲线绘制的学习,今天准备学习UI绘制中经常会用到的跟Path相关的一些知识,也是很重 ...
- Android高级UI组件progressbar进度条
前两天学习了datepicker日历,time picker时间以及chronrmeter计时器的基础控件,今天浅浅了解一下Android其中的一个高级UI组件progressbar吧,进度条基本是每 ...
最新文章
- 2022-2028年中国聚硫橡胶行业市场研究及前瞻分析报告
- UVA10212 【The Last Non-zero Digit.】
- Java面试中最高频的那20%知识点是什么?
- HTML初级知识点总结(1.0)
- Hibernate核心配置文件cfg参数说明
- 数据与智能武装营销飞轮,网易探索C2B时代营销新玩法
- PHP中错误处理集合
- python面向对象编程从零开始_Python面向对象编程从零开始,从没对象到有对象
- php获取服务器数据库信息,PHP获取服务器信息
- 26秒!全球销量第一的AI音箱就被腾讯黑掉了,然后变身窃听器
- java对excel加密_Java 加密、解密Excel文档
- 使谷歌浏览器兼容ie浏览器
- DO-218AB封装,SM8T系列,SM8S系列加强版,汽车级TVS二极管
- SAI2和PS如何查看当前鼠标位置内容的图层
- php工具apache启动失败,phpstudy中apache启动失败的原因是什么?
- 100年来诺贝尔化学奖获得者
- 小米手机计算机软件,手机计算器
- CART分类树原理及示例
- Linux | 第一篇——常见指令汇总【超全、超详细讲解】
- 80后的 我们 依然单身
热门文章
- oracle recover datafile,recover datafile和alter database recover datafile;
- RegNet网络结构与搭建
- 群发邮件服务器软件,邮件服务器软件:邮件群发是否已死?
- 华为公司内部控制手册(干货)
- Node.js版-七夕无事,人艰勿拆,求别说...
- 阿里云服务器价格表,学会利用价格计算器查询阿里云服务器购买价格
- 技巧分享篇---如何从GitHub上下载某个项目中单个文件的方法
- 百度导航SDK升级适配 Anroid 11骑步导航闪退
- mysql relay_mysql relay log参数汇总
- 系统集成项目管理工程师备考资料(口袋应试第二版)14