AnimatedPathView实现自定义图片标签
老早用过小红书app,对于他们客户端笔记这块的设计非常喜欢,恰好去年在小红书的竞争对手公司,公司基于产品的考虑和产品的发展,也需要将app社交化,于是在社区分享这块多多少少参照了小红书的设计,这里面就有一个比较有意思的贴纸,标签等设计,这里用到了GpuImage的库,这个demo我也将代码开源了,有需要的去fork我的github的代码,今天要说的是详情页面的AnimatedPathView实现可以动起来的标签。(之前我们项目中由于时间问题,将这种效果用h5实现了,不过现在回React Native之后,发现实现起来更简单了),今天要说的是用android实现这种效果。
且看个效果图:
要实现我们这样的效果,首先分析下,线条的绘制和中间圆圈的实现,以及文字的绘制。
对于线条的绘制我们不多说,直接canvas.DrawLine,不过这种线条是死的,不能实现运动的效果,还好Java为我们提供了另一个方法,我们可以用Path去实现,之前做腾讯手写板的时候也是这么做的(可以点击链接查看效果,不过代码没办法公开),点击打开链接,通过上面说的,我们改变PathEffect的偏移量就可以改变path显示的长度,从而实现动画的效果。而PathEffect有很多子类,从而满足不同的效果,这里不再说明。
float percentage = 0.0f;
PathEffect effect = new DashPathEffect(new float[]{pathLength, pathLength}, pathLength - pathLength*percentage);
这里贴出AnimatedPathView的完整代码:
public class AnimatedPathView extends View {
private Paint mPaint;
private Path mPath;
private int mStrokeColor = Color.parseColor("#ff6c6c");
private int mStrokeWidth = 8;
private float mProgress = 0f;
private float mPathLength = 0f;
private float circleX = 0f;
private float circleY = 0f;
private int radius = 0;
private String pathText="化妆包...";
private int textX,textY;
public AnimatedPathView(Context context) {
this(context, null);
init();
}
public AnimatedPathView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
init();
}
public AnimatedPathView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AnimatedPathView);
mStrokeColor = a.getColor(R.styleable.AnimatedPathView_pathColor, Color.parseColor("#ff6c6c"));
mStrokeWidth = a.getInteger(R.styleable.AnimatedPathView_pathWidth, 8);
a.recycle();
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(mStrokeColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setAntiAlias(true);
setPath(new Path());
}
public void setPath(Path p) {
mPath = p;
PathMeasure measure = new PathMeasure(mPath, false);
mPathLength = measure.getLength();
}
public void setPathText(String pathText,int textX,int textY ) {
this.pathText=pathText;
this.textX=textX;
this.textY=textY;
}
public void setPath(float[]... points) {
if (points.length == 0)
throw new IllegalArgumentException("Cannot have zero points in the line");
Path p = new Path();
p.moveTo(points[0][0], points[0][1]);
for (int i = 1; i < points.length; i++) {
p.lineTo(points[i][0], points[i][1]);
}
//将第一个xy坐标点作为绘制的原点
circleX = points[0][0] - radius / 2;
circleY = points[0][1] - radius / 2;
setPath(p);
}
public void setPercentage(float percentage) {
if (percentage < 0.0f || percentage > 1.0f)
throw new IllegalArgumentException("setPercentage not between 0.0f and 1.0f");
mProgress = percentage;
invalidate();
}
public void scalePathBy(float x, float y) {
Matrix m = new Matrix();
m.postScale(x, y);
mPath.transform(m);
PathMeasure measure = new PathMeasure(mPath, false);
mPathLength = measure.getLength();
}
public void scaleCircleRadius(int radius) {
this.radius = radius;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制圆形
// drawCircle(canvas);
//绘线条
drawPathEffect(canvas);
//绘制文字
drawText(canvas);
canvas.restore();
}
private void drawText(Canvas canvas) {
mPaint.setTextSize(28);
mPaint.setColor(Color.parseColor("#ffffff"));
if (canvas!=null&& !TextUtils.isEmpty(pathText)){
canvas.drawText(pathText,textX,textY,mPaint);
}
invalidate();
}
private void drawPathEffect(Canvas canvas) {
PathEffect pathEffect = new DashPathEffect(new float[]{mPathLength, mPathLength}, (mPathLength - mPathLength * mProgress));
mPaint.setPathEffect(pathEffect);
mPaint.setStrokeWidth(4);
mPaint.setColor(Color.parseColor("#ffffff"));
canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop());
canvas.drawPath(mPath, mPaint);
}
private void drawCircle(Canvas canvas) {
int strokenWidth = 25;
mPaint.setStrokeWidth(strokenWidth);
mPaint.setColor(Color.parseColor("#ffffff"));
canvas.drawCircle(circleX, circleY, radius , mPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(widthMeasureSpec);
int measuredWidth, measuredHeight;
if (widthMode == MeasureSpec.AT_MOST)
throw new IllegalStateException("AnimatedPathView cannot have a WRAP_CONTENT property");
else
measuredWidth = widthSize;
if (heightMode == MeasureSpec.AT_MOST)
throw new IllegalStateException("AnimatedPathView cannot have a WRAP_CONTENT property");
else
measuredHeight = heightSize;
setMeasuredDimension(measuredWidth, measuredHeight);
}
}
这段代码借鉴了点击打开链接的部分代码,并在此基础上做了更多的判断和改变,以满足本文开头说说的那种需要,上面的代码只是实现了画线条的效果,那么如何实现中间圆圈的闪烁呢,其实也很简单,我们可以用动画来实现(View动画),这里我们大可以自己自定义一个View实现,而这个View包含了圆圈闪烁和画线,按照上面的逻辑我们写一个自定义的View,代码如下:
public class PointView extends FrameLayout {
private Context mContext;
private List<PointScaleBean> points;
private FrameLayout layouPoints;
private AnimatedPathView animatedPath;
private int radius=10;
private String text="图文标签 $99.00";
public PointView(Context context) {
this(context, null);
}
public PointView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PointView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
this.mContext = context;
View imgPointLayout = inflate(context, R.layout.layout_point, this);
layouPoints = (FrameLayout) imgPointLayout.findViewById(R.id.layouPoints);
animatedPath=(AnimatedPathView) imgPointLayout.findViewById(R.id.animated_path);
}
public void addPoints(int width, int height) {
addPoint(width, height);
}
public void setPoints(List<PointScaleBean> points) {
this.points = points;
}
private void addPoint(int width, int height) {
layouPoints.removeAllViews();
for (int i = 0; i < points.size(); i++) {
double width_scale = points.get(i).widthScale;
double height_scale = points.get(i).heightScale;
LinearLayout view = (LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.layout_img_point, this, false);
ImageView imageView = (ImageView) view.findViewById(R.id.imgPoint);
imageView.setTag(i);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
animationDrawable.start();
LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
layoutParams.leftMargin = (int) (width * width_scale);
layoutParams.topMargin = (int) (height * height_scale);
// imageView.setOnClickListener(this);
layouPoints.addView(view, layoutParams);
}
initView();
initPathAnimated();
}
private void initPathAnimated() {
ViewTreeObserver observer = animatedPath.getViewTreeObserver();
if(observer != null){
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
animatedPath.getViewTreeObserver().removeGlobalOnLayoutListener(this);
animatedPath.scaleCircleRadius(radius);
animatedPath.scalePathBy(animatedPath.getWidth()/2,animatedPath.getHeight()/2);
float[][] points = new float[][]{
{animatedPath.getWidth()/2-radius/2,animatedPath.getHeight()/2-radius/2},
{animatedPath.getWidth()/2- UIUtils.dp2px(mContext,30), animatedPath.getHeight()/2- UIUtils.dp2px(mContext,40)},
{animatedPath.getWidth()/2-UIUtils.dp2px(mContext,150), animatedPath.getHeight()/2- UIUtils.dp2px(mContext,40)},
};
animatedPath.setPath(points);
// animatedPath.setPathText(text,animatedPath.getWidth()/2-UIUtils.dp2px(mContext,150), animatedPath.getHeight()/2- UIUtils.dp2px(mContext,50));
}
});
}
}
private void initView() {
animatedPath.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "percentage", 0.0f, 1.0f);
anim.setDuration(2000);
anim.setInterpolator(new LinearInterpolator());
anim.start();
}
});
}
}
上面对应的布局和资源文件:
layou_point.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<com.yju.app.widght.path.AnimatedPathView
android:id="@+id/animated_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<FrameLayout
android:id="@+id/layouPoints"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
layout_img_point.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/imgPoint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/point_img" />
</LinearLayout>
文中用到的Anim就是帧动画了,
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@drawable/point_img1"
android:duration="100" />
....省略n多图片资源
<item
android:drawable="@drawable/point_img13"
android:duration="100" />
</animation-list>
而最后我们只需要在我们自己的MainActivity中添加简单的代码既可实现上面的效果:
private void initPointView() {
List<PointScaleBean> list=new ArrayList<>();
PointScaleBean point=new PointScaleBean();
point.widthScale = 0.36f;
point.heightScale = 0.75f;
list.add(point);
pointView.setPoints(list);
pointView.addPoints(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
}
对于布局我是这么做的,将View的父布局的背景加一个图片,实际的开发中大家可以写一个相对的布局,这个就能实现实时的效果了,好了就写到这里,有疑问请留言或者加群(278792776)。
附件:一个滤镜效果:点击打开链接
AnimatedPathView实现自定义图片标签的更多相关文章
- CKEditor5 + vue2.0 自定义图片上传、highlight、字体等用法
因业务需求,要在 vue2.0 的项目里使用富文本编辑器,经过调研多个编辑器,CKEditor5 支持 vue,遂采用.因 CKEditor5 文档比较少,此处记录下引用和一些基本用法. CKEdit ...
- DedeCMS织梦自定义图片字段调用出现{dede:img ..}
做站过程中碰到这样一个问题,找到解决办法收藏分享:为什么在首页用自定义列表调用出来的图片字段不是正确的图片地址,而是类似于: {dede:img text='' width='270' height= ...
- TensorFlow2.0(10):加载自定义图片数据集到Dataset
.caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...
- [19/06/04-星期二] HTML基础_实体(转义字符)、图片标签(img)、元标签(meta)、语法规范、内联框架(iframe)、超链接
一.实体(转义字符) 在HTML中,一些诸如<.> 就是普通的小于号和大于号不能直接使用,因为浏览可能会把它当成一个标签去解析,所以需要一些特殊字符去表示这些特殊字符, 这些字符我们称他们 ...
- [19/05/14-星期二] HTML_body标签(列表标签和图片标签)
一.列表标签 <!-- 快捷键 1.<meta charset="UTF-8"/> 用m6可直接写出 2.复制当前1行到下一行 ctrl+shift+R --&g ...
- DEDE建站之图片标签技巧指南
做dede站的时候,曾经遇到很苦恼的事情,就是给图片集添加了一个网上下载下来的特效,需要给图片的链接上添加一个rel属性,供JS调用以达到那种特效.但是当时只知道dede的图片链接标签是[field: ...
- 使用自定义tld标签简化jsp的繁琐操作
最近做一个树形结构的展示,请求目标页面后,后台只返回简单的List,虽然有想过在jsp页面内做一些操作简化,但是太繁琐了,其他的标签又不能满足需求,所以只能自己做一个.使用tld标签可以简化jsp代码 ...
- php正则获取html图片标签信息(采集图片)
php获取html图片标签信息(采集图片),实现图片采集及其他功能,带代码如下: <?php $str="<img src='./a.jpg'/>111111<img ...
- 网页qq客服代码并自定义图片
<script>var online= new Array();</script> <script src="http://webpresence.qq.com ...
随机推荐
- 360面试-C++后端(实习)
在线远程视频面试 一面: 自我介绍. 知道哪几种排序算法,各算法的时间复杂度. 解决hash冲突的几种方式. 有哪些方法清除cache中旧的数据.不太清楚,我扯到了操作系统中缺页中断的页面置换原理上, ...
- Android SDK下载安装及配置教程
2017年12月07日 13:33:32 4942 转载自:http://blog.csdn.net/dr_neo/article/details/49870587 Android开发环境搭建可以分为 ...
- DecimalFormat(用于格式化十进制数字)
1概念: DecimalFormat 是 NumberFormat 的一个具体子类,用于格式化十进制数字.该类设计有各种功能,使其能够分析和格式化任意语言环境中的数,包括对西方语言.阿拉伯语和印度语数 ...
- 如何上传本地项目到gitHub解决方案
最近有人有人问到我怎么将新创建的本地代码上传到github上,这里简单的记录一下,我喜欢使用命令行,这里全用命令行来实现,不了解Git命令的可以去了解下. 1. 建立本地仓库,cd到你想要上传文件的 ...
- NVIDIA Titan Xp Star Wars Collector's Edition显卡深度学习工作站 + Ubuntu17.10 + Tensorflow-gpu + Anaconda3 + Python 3.6 设置
为了能让 Tensorflow GPU 版本跑起来,我折腾了1个多星期. 总体参照 https://zhuanlan.zhihu.com/p/32118549 ,安装成功,但还是有不足的地方, 在此记 ...
- SignalR Progress
Server public class ServerHub : Hub { public async Task<string> ALongTimeTask() { var p = new ...
- Angular4.0入门
angular与其他的差别 angular cli安装 cnpm install -g @angular/cli 最新版本 cnpm uninstall -g @angular/cli 卸载全局版本 ...
- Go 语言Map(集合)
Map 是一种无序的键值对的集合.Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值. Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它.不过,Map 是无 ...
- Dynamics CRM2016 Web API之删除单个查找字段值
之前的博文中有介绍过,Web Api中的一个删除单个属性的Api但没提供查找字段的删除方法,本篇补充上,这里给出的示例代码是C#的(主要看url的拼接),看下url中最后的/$ref,这个标示表明了当 ...
- Bootstrap3 栅格系统-实例:从堆叠到水平排列
使用单一的一组 .col-md-* 栅格类,就可以创建一个基本的栅格系统,在手机和平板设备上一开始是堆叠在一起的(超小屏幕到小屏幕这一范围),在桌面(中等)屏幕设备上变为水平排列.所有"列( ...