博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AnimatedPathView实现自定义图片标签
阅读量:5835 次
发布时间:2019-06-18

本文共 10052 字,大约阅读时间需要 33 分钟。

老早用过小红书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
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
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

layout_img_point.xml

文中用到的Anim就是帧动画了,

....省略n多图片资源
而最后我们只需要在我们自己的MainActivity中添加简单的代码既可实现上面的效果:

private void initPointView() {        List
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的父布局的背景加一个图片,实际的开发中大家可以写一个相对的布局,这个就能实现实时的效果了,好了就写到这里,有疑问请留言或者加群。

你可能感兴趣的文章
listbox用法
查看>>
冲刺第九天 1.10 THU
查看>>
传值方式:ajax技术和普通传值方式
查看>>
Linux-网络连接-(VMware与CentOS)
查看>>
寻找链表相交节点
查看>>
AS3——禁止swf缩放
查看>>
linq 学习笔记之 Linq基本子句
查看>>
[Js]布局转换
查看>>
Hot Bath
查看>>
国内常用NTP服务器地址及
查看>>
Java annotation 自定义注释@interface的用法
查看>>
Apache Spark 章节1
查看>>
phpcms与discuz的ucenter整合
查看>>
Linux crontab定时执行任务
查看>>
mysql root密码重置
查看>>
33蛇形填数
查看>>
选择排序
查看>>
SQL Server 数据库的数据和日志空间信息
查看>>
前端基础之JavaScript
查看>>
自己动手做个智能小车(6)
查看>>