AnimatedPathView实现自定义图片标签,让图片动起来
2016-09-29
665
老早用过小红书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);
- }
- }
- 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>
- 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)。
