欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义View绘图实现拖影动画

程序员文章站 2024-03-05 23:57:55
前几天在“android绘图之渐隐动画”一文中通过画线实现了渐隐动画,但里面有个问题,画笔较粗(大于1)时线段之间会有裂隙,我又改进了一下。这次效果好多了。 先看效果吧:...

前几天在“android绘图之渐隐动画”一文中通过画线实现了渐隐动画,但里面有个问题,画笔较粗(大于1)时线段之间会有裂隙,我又改进了一下。这次效果好多了。

先看效果吧:

Android自定义View绘图实现拖影动画

然后我们来说说基本的做法:
 •根据画笔宽度,计算每一条线段两个顶点对应的四个点,四点连线,包围线段,形成一个路径。
 •后一条线段的路径的前两个点,取(等于)前一条线段的后两点,这样就衔接起来了。 

把path的style修改为fill,效果是这样的:

Android自定义View绘图实现拖影动画

可以看到一个个四边形,连成了路径。

好啦,现在说说怎样根据两点计算出包围它们连线的路径所需的四个点。

先看一张图:

Android自定义View绘图实现拖影动画

在这张图里,黑色细线是我们拿到的两个触摸点相连得到的。当画笔宽度大于1(比如为10)时,其实经过这条黑线的两个端点并且与这条黑线垂直相交的两条线(蓝线),就可以计算出来,蓝线的长度就是画笔的宽度,结合这些就可以计算出红色的四个点。而红色的四个点就围住了线段,形成路径。

这里面用到两点连线的公式,采用点斜式:

y = k*x + b

黑线的斜率是:

k = (y2 - y1) / (x2 - x1) 

垂直相交的两条线的斜率的关系是:

k1 * k2 = -1 

所以,蓝线的斜率就可以计算出来了。有了斜率和线上的一个点,就可以求出这条线的点斜式中的b,点斜式就出来了。

然后,利用两点间距离公式:

Android自定义View绘图实现拖影动画

已知一个点,这个点与另一个点的距离(画笔宽度除以2),斜率,代入两点间距离公式和蓝线的点斜式,就可以计算出两个红色的点了。

计算时用到的是一元二次方程a*x*x + bx + c = 0,求 x 时用的公式是:

Android自定义View绘图实现拖影动画

好啦,最后,上代码:

package com.example.disappearinglines;

import android.content.context;
import android.graphics.canvas;
import android.graphics.paint;
import android.graphics.path;
import android.graphics.pointf;
import android.graphics.rectf;
import android.os.handler;
import android.os.message;
import android.os.systemclock;
import android.support.annotation.nonnull;
import android.util.attributeset;
import android.util.log;
import android.util.typedvalue;
import android.view.motionevent;
import android.view.view;

import java.util.arraylist;
import java.util.collection;
import java.util.iterator;
import java.util.list;
import java.util.listiterator;

/**
 * created by foruok,欢迎关注我的订阅号“程序视界”.
 */

public class disappearingdoodleview extends view {
 public static float convertdiptopx(context context, float fdip) {
  float fpx = typedvalue.applydimension(typedvalue.complex_unit_dip, fdip,
    context.getresources().getdisplaymetrics());
  return fpx;
 }

 final static string tag = "doodleview";
 class lineelement {
  static final public int alpha_step = 8;
  public lineelement(float pathwidth){
   mpaint = new paint();
   mpaint.setargb(255, 255, 0, 0);
   mpaint.setantialias(true);
   mpaint.setstrokewidth(0);
   mpaint.setstrokecap(paint.cap.butt);
   mpaint.setstyle(paint.style.fill);
   mpath = new path();
   mpathwidth = pathwidth;
   for(int i= 0; i < mpoints.length; i++){
    mpoints[i] = new pointf();
   }
  }

  public void setpaint(paint paint){
   mpaint = paint;
  }

  public void setalpha(int alpha){
   mpaint.setalpha(alpha);
   mpathwidth = (alpha * mpathwidth) / 255;
  }

  private boolean caculatepoints(float k, float b, float x1, float y1, float distance, pointf pt1, pointf pt2){
   //point-k formula
   // y= kx + b
   //distance formula of two points
   // distance*distance = math.pow((x - x1), 2) + math.pow((y - y1), 2)
   // |
   // v
   // ax*x + bx + c = 0;
   // |
   // v
   // x = (-b +/- math.sqrt( b*b - 4*a*c ) ) / (2*a)
   double a1 = math.pow(k, 2) + 1;
   double b1 = 2* k * (b - y1) - 2 * x1;
   double c1 = math.pow(x1, 2) + math.pow(b - y1, 2) - math.pow(distance, 2);
   double criterion = math.pow(b1, 2) - 4*a1*c1;
   if(criterion > 0) {
    criterion = math.sqrt(criterion);
    pt1.x = (float) ((-b1 + criterion) / (2 * a1));
    pt1.y = k * pt1.x + b;
    pt2.x = (float) ((-b1 - criterion) / (2 * a1));
    pt2.y = k * pt2.x + b;
    return true;
   }
   return false;
  }

  private void swappoint(pointf pt1, pointf pt2){
   float t = pt1.x;
   pt1.x = pt2.x;
   pt2.x = t;
   t = pt1.y;
   pt1.y = pt2.y;
   pt2.y = t;
  }

  public boolean updatepathpoints(){
   float distance = mpathwidth / 2;
   if(math.abs(mendx - mstartx) < 1){
    mpoints[0].x = mstartx + distance;
    mpoints[0].y = mstarty - distance;
    mpoints[1].x = mstartx - distance;
    mpoints[1].y = mpoints[0].y;
    mpoints[2].x = mpoints[1].x;
    mpoints[2].y = mendy + distance;
    mpoints[3].x = mpoints[0].x;
    mpoints[3].y = mpoints[2].y;
   }else if(math.abs(mendy - mstarty) < 1){
    mpoints[0].x = mstartx - distance;
    mpoints[0].y = mstarty - distance;
    mpoints[1].x = mpoints[0].x;
    mpoints[1].y = mstarty + distance;
    mpoints[2].x = mendx + distance;
    mpoints[2].y = mpoints[1].y;
    mpoints[3].x = mpoints[2].x;
    mpoints[3].y = mpoints[0].y;
   }else{
    //point-k formula
    //y= kx + b
    float kline = (mendy - mstarty) / (mendx - mstartx);
    float kvertline = -1 / kline;
    float b = mstarty - (kvertline * mstartx);
    if(!caculatepoints(kvertline, b, mstartx, mstarty, distance, mpoints[0], mpoints[1])){
     string info = string.format(tag, "startpt, criterion < 0, (%.2f, %.2f)-->(%.2f, %.2f), kline - %.2f, kvertline - %.2f, b - %.2f",
       mstartx, mstarty, mendx, mendy, kline, kvertline, b);
     log.i(tag, info);
     return false;
    }
    b = mendy - (kvertline * mendx);
    if(!caculatepoints(kvertline, b, mendx, mendy, distance, mpoints[2], mpoints[3])){
     string info = string.format(tag, "endpt, criterion < 0, (%.2f, %.2f)-->(%.2f, %.2f), kline - %.2f, kvertline - %.2f, b - %.2f",
       mstartx, mstarty, mendx, mendy, kline, kvertline, b);
     log.i(tag, info);
     return false;
    }
    //reorder points to unti-clockwise
    if(mstartx < mendx){
     if(mstarty < mendy){
      if(mpoints[0].x < mpoints[1].x){
       swappoint(mpoints[0], mpoints[1]);
      }
      if(mpoints[2].x > mpoints[3].x){
       swappoint(mpoints[2], mpoints[3]);
      }
     }else{
      if(mpoints[0].x > mpoints[1].x){
       swappoint(mpoints[0], mpoints[1]);
      }
      if(mpoints[2].x < mpoints[3].x){
       swappoint(mpoints[2], mpoints[3]);
      }
     }
    }else{
     if(mstarty < mendy){
      if(mpoints[0].x < mpoints[1].x){
       swappoint(mpoints[0], mpoints[1]);
      }
      if(mpoints[2].x > mpoints[3].x){
       swappoint(mpoints[2], mpoints[3]);
      }
     }else{
      if(mpoints[0].x > mpoints[1].x){
       swappoint(mpoints[0], mpoints[1]);
      }
      if(mpoints[2].x < mpoints[3].x){
       swappoint(mpoints[2], mpoints[3]);
      }
     }
    }
   }

   return true;
  }

  // for the first line
  public void updatepath(){
   //update path
   mpath.reset();
   mpath.moveto(mpoints[0].x, mpoints[0].y);
   mpath.lineto(mpoints[1].x, mpoints[1].y);
   mpath.lineto(mpoints[2].x, mpoints[2].y);
   mpath.lineto(mpoints[3].x, mpoints[3].y);
   mpath.close();
  }

  // for middle line
  public void updatepathwithstartpoints(pointf pt1, pointf pt2){
   mpath.reset();
   mpath.moveto(pt1.x, pt1.y);
   mpath.lineto(pt2.x, pt2.y);
   mpath.lineto(mpoints[2].x, mpoints[2].y);
   mpath.lineto(mpoints[3].x, mpoints[3].y);
   mpath.close();
  }

  public float mstartx = -1;
  public float mstarty = -1;
  public float mendx = -1;
  public float mendy = -1;
  public paint mpaint;
  public path mpath;
  public pointf[] mpoints = new pointf[4]; //path's vertex
  float mpathwidth;
 }

 private lineelement mcurrentline = null;
 private list<lineelement> mlines = null;
 private float mlaserx = 0;
 private float mlasery = 0;
 final paint mpaint = new paint();
 private int mwidth = 0;
 private int mheight = 0;
 private long melapsed = 0;
 private float mstrokewidth = 20;
 private float mcircleradius = 10;
 private handler mhandler = new handler(){
  @override
  public void handlemessage(message msg){
   disappearingdoodleview.this.invalidate();
  }
 };

 public disappearingdoodleview(context context){
  super(context);
  initialize(context);
 }

 public disappearingdoodleview(context context, attributeset attrs){
  super(context, attrs);
  initialize(context);
 }

 private void initialize(context context){
  mstrokewidth = convertdiptopx(context, 22);
  mcircleradius = convertdiptopx(context, 10);
  mpaint.setargb(255, 255, 0, 0);
  mpaint.setantialias(true);
  mpaint.setstrokewidth(0);
  mpaint.setstyle(paint.style.fill);
 }

 @override
 protected void onsizechanged (int w, int h, int oldw, int oldh){
  mwidth = w;
  mheight = h;
  adjustlasterposition();
 }

 private void adjustlasterposition(){
  if(mlaserx - mcircleradius < 0) mlaserx = mcircleradius;
  else if(mlaserx + mcircleradius > mwidth) mlaserx = mwidth - mcircleradius;
  if(mlasery - mcircleradius < 0) mlasery = mcircleradius;
  else if(mlasery + mcircleradius > mheight) mlasery = mheight - mcircleradius;
 }

 private void updatelaserposition(float x, float y){
  mlaserx = x;
  mlasery = y;
  adjustlasterposition();
 }
 @override
 protected void ondraw(canvas canvas){
  //canvas.drawtext("abcde", 10, 16, mpaint);
  melapsed = systemclock.elapsedrealtime();

  if(mlines != null) {
   updatepaths();
   for (lineelement e : mlines) {
    if(e.mstartx < 0 || e.mendy < 0 || e.mpath.isempty()) continue;
    //canvas.drawline(e.mstartx, e.mstarty, e.mendx, e.mendy, e.mpaint);
    canvas.drawpath(e.mpath, e.mpaint);
   }
   compactpaths();
  }
  canvas.drawcircle(mlaserx, mlasery, mcircleradius, mpaint);
 }

 private boolean isvalidline(float x1, float y1, float x2, float y2){
  return math.abs(x1 - x2) > 1 || math.abs(y1 - y2) > 1;
 }

 @override
 public boolean ontouchevent(motionevent event){
  float x = event.getx();
  float y = event.gety();

  int action = event.getaction();
  if(action == motionevent.action_up){// end one line after finger release
   if(isvalidline(mcurrentline.mstartx, mcurrentline.mstarty, x, y)){
    mcurrentline.mendx = x;
    mcurrentline.mendy = y;
    addtopaths(mcurrentline);
   }
   //mcurrentline.updatepathpoints();
   mcurrentline = null;
   updatelaserposition(x, y);
   invalidate();
   return true;
  }

  if(action == motionevent.action_down){
   mlines = null;
   mcurrentline = new lineelement(mstrokewidth);

   mcurrentline.mstartx = x;
   mcurrentline.mstarty = y;
   updatelaserposition(x, y);
   return true;
  }

  if(action == motionevent.action_move) {
   if(isvalidline(mcurrentline.mstartx, mcurrentline.mstarty, x, y)){
    mcurrentline.mendx = x;
    mcurrentline.mendy = y;
    addtopaths(mcurrentline);

    mcurrentline = new lineelement(mstrokewidth);
    mcurrentline.mstartx = x;
    mcurrentline.mstarty = y;

    updatelaserposition(x, y);
   }else{
    //do nothing, wait next point
   }
  }

  if(mhandler.hasmessages(1)){
   mhandler.removemessages(1);
  }
  message msg = new message();
  msg.what = 1;
  mhandler.sendmessagedelayed(msg, 0);

  return true;
 }

 private void addtopaths(lineelement element){
  if(mlines == null) {
   mlines = new arraylist<lineelement>() ;
  }
  mlines.add(element);
 }

 private void updatepaths() {
  int size = mlines.size();
  if (size == 0) return;


  lineelement line = null;
  int j = 0;
  for (; j < size; j++) {
   line = mlines.get(j);
   if (line.updatepathpoints()) break;
  }

  if (j == size) {
   mlines.clear();
   return;
  } else {
   for (j--; j >= 0; j--) {
    mlines.remove(0);
   }
  }

  line.updatepath();
  size = mlines.size();

  lineelement lastline = null;
  for (int i = 1; i < size; i++) {
   line = mlines.get(i);
   if (line.updatepathpoints()){
    if (lastline == null) {
     lastline = mlines.get(i - 1);
    }
    line.updatepathwithstartpoints(lastline.mpoints[3], lastline.mpoints[2]);
    lastline = null;
   }else{
    mlines.remove(i);
    size = mlines.size();
   }
  }
 }

 public void compactpaths(){

  int size = mlines.size();
  int index = size - 1;
  if(size == 0) return;
  int basealpha = 255 - lineelement.alpha_step;
  int itselfalpha;
  lineelement line;
  for(; index >=0 ; index--, basealpha -= lineelement.alpha_step){
   line = mlines.get(index);
   itselfalpha = line.mpaint.getalpha();
   if(itselfalpha == 255){
    if(basealpha <= 0 || line.mpathwidth < 1){
     ++index;
     break;
    }
    line.setalpha(basealpha);
   }else{
    itselfalpha -= lineelement.alpha_step;
    if(itselfalpha <= 0 || line.mpathwidth < 1){
     ++index;
     break;
    }
    line.setalpha(itselfalpha);
   }
  }

  if(index >= size){
   // all sub-path should disappear
   mlines = null;
  }
  else if(index >= 0){
   //log.i(tag, "compactpaths from " + index + " to " + (size - 1));
   mlines = mlines.sublist(index, size);
  }else{
   // no sub-path should disappear
  }

  long interval = 40 - systemclock.elapsedrealtime() + melapsed;
  if(interval < 0) interval = 0;
  message msg = new message();
  msg.what = 1;
  mhandler.sendmessagedelayed(msg, interval);
 }
}

这样自绘,效率不太好,还没想怎么去改进,大家可以讨论讨论。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。