Android自定义View绘图实现拖影动画
前几天在“android绘图之渐隐动画”一文中通过画线实现了渐隐动画,但里面有个问题,画笔较粗(大于1)时线段之间会有裂隙,我又改进了一下。这次效果好多了。
先看效果吧:
然后我们来说说基本的做法:
•根据画笔宽度,计算每一条线段两个顶点对应的四个点,四点连线,包围线段,形成一个路径。
•后一条线段的路径的前两个点,取(等于)前一条线段的后两点,这样就衔接起来了。
把path的style修改为fill,效果是这样的:
可以看到一个个四边形,连成了路径。
好啦,现在说说怎样根据两点计算出包围它们连线的路径所需的四个点。
先看一张图:
在这张图里,黑色细线是我们拿到的两个触摸点相连得到的。当画笔宽度大于1(比如为10)时,其实经过这条黑线的两个端点并且与这条黑线垂直相交的两条线(蓝线),就可以计算出来,蓝线的长度就是画笔的宽度,结合这些就可以计算出红色的四个点。而红色的四个点就围住了线段,形成路径。
这里面用到两点连线的公式,采用点斜式:
y = k*x + b
黑线的斜率是:
k = (y2 - y1) / (x2 - x1)
垂直相交的两条线的斜率的关系是:
k1 * k2 = -1
所以,蓝线的斜率就可以计算出来了。有了斜率和线上的一个点,就可以求出这条线的点斜式中的b,点斜式就出来了。
然后,利用两点间距离公式:
已知一个点,这个点与另一个点的距离(画笔宽度除以2),斜率,代入两点间距离公式和蓝线的点斜式,就可以计算出两个红色的点了。
计算时用到的是一元二次方程a*x*x + bx + c = 0,求 x 时用的公式是:
好啦,最后,上代码:
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); } }
这样自绘,效率不太好,还没想怎么去改进,大家可以讨论讨论。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: C++数据结构中算法的使用
下一篇: JAVA源码学习(二)——Arrays类