前几天在“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);
}
}

这样自绘,效率不太好,还没想怎么去改进,哪位有办法,能够留言给我。

^_^


參考:

Android自己定义View画图实现拖影动画的更多相关文章

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

    前几天在"Android绘图之渐隐动画"一文中通过画线实现了渐隐动画,但里面有个问题,画笔较粗(大于1)时线段之间会有裂隙,我又改进了一下.这次效果好多了. 先看效果吧: 然后我们 ...

  2. Android 自己定义View (二) 进阶

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自己定义View之旅.前面已经介绍过一个自己定义View的基础 ...

  3. Android 自己定义View须要重写ondraw()等方法

    Android  自己定义View须要重写ondraw()等方法.这篇博客给大家说说自己定义View的写法,须要我们继承View,然后重写一些 方法,方法多多,看你须要什么方法 首先写一个自己定义的V ...

  4. 【Android自己定义View实战】之自己定义超简单SearchView搜索框

    [Android自己定义View实战]之自己定义超简单SearchView搜索框 这篇文章是对之前文章的翻新,至于为什么我要又一次改动这篇文章?原因例如以下 1.有人举报我抄袭,原文链接:http:/ ...

  5. Android 自己定义View学习(2)

    上一篇学习了基本使用方法,今天学一下略微复杂一点的.先看一下效果图 为了完毕上面的效果还是要用到上一期开头的四步 1,属性应该要有颜色,要有速度 <?xml version="1.0& ...

  6. Android自己定义view之measure、layout、draw三大流程

    自己定义view之measure.layout.draw三大流程 一个view要显示出来.须要经过測量.布局和绘制这三个过程,本章就这三个流程具体探讨一下.View的三大流程具体分析起来比較复杂,本文 ...

  7. 手把手带你画一个 时尚仪表盘 Android 自己定义View

    拿到美工效果图.咱们程序猿就得画得一模一样. 为了不被老板喷,仅仅能多练啊. 听说你认为前面几篇都so easy,那今天就带你做个相对照较复杂的. 转载请注明出处:http://blog.csdn.n ...

  8. Android 自己定义View (四) 视频音量调控

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24529807 今天没事逛eoe,看见有人求助要做一个以下的效果,我看以下一哥们说 ...

  9. Android自己定义View的实现方法

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17357967 不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了.回 ...

随机推荐

  1. Quartus II sof文件转 jic文件

    选择File->Convert Programming Files... Programming File Type选择JTAG Indirect ConfigurationFile(.jic) ...

  2. Codeforces 429D Tricky Function 近期点对

    题目链接:点击打开链接 暴力出奇迹. 正解应该是近期点对.以i点为x轴,sum[i](前缀和)为y轴,求随意两点间的距离. 先来个科学的暴力代码: #include<stdio.h> #i ...

  3. codeforce 571 B Minimization

    题意:给出一个序列,经过合适的排序后.使得最小. 做法:将a升序排序后,dp[i][j]:选择i个数量为n/k的集合,选择j个数量为n/k+1的集合的最小值. 举个样例, a={1,2,3,4,5,6 ...

  4. 洛谷P2115 [USACO14MAR]破坏Sabotage

    题目描述 Farmer John's arch-nemesis, Farmer Paul, has decided to sabotage Farmer John's milking equipmen ...

  5. 深入理解Android(2)——理解Android中的JNI(中)

    阳光小强参加了CSDN博客之星评选,如果你觉得阳光小强的博客对你有所帮助,为小强投上一票吧:http://vote.blog.csdn.net/blogstar2014/details?usernam ...

  6. Kinect 开发 —— 开发前的准备工作

    Kinect SDK v1.5 支持托管语言和非托管语言 Xbox360的游戏是基于Xbox360开发工具包 (XDK)开发的,Xbox 360和Windows是两个完全不同的系统架构.使用Kinec ...

  7. XMPP开发之从零開始

    对于server的搭建和设置.我在这里就不再多说了.有好多前辈已经帮大家攻克了.能够參考下这篇博客 XMPPserver配置 我依照这个博客配置好了,server后,然后在网上參照代码写了一个小的de ...

  8. ZOJ Problem Set - 3819Average Score

    ZOJ Problem Set - 3819Average Score 题目链接 题目大意:给你两个班的的学生的分数(A,B班).A班有一个学生的分数没有给出. 如今要求你给出这个学生分数的上下限.使 ...

  9. 华为畅玩5 (CUN-AL00) 刷入第三方twrp Recovery 及 root

    华为畅玩5 (CUN-AL00) 刷入第三方twrp Recovery  及 root 下载地址    http://pan.baidu.com/s/1hsn6VzA 1. 在官网申请解锁码    申 ...

  10. Math的三个取整方法。

    Math类中提供了三个与取整有关的方法:ceil.floor.round,这些方法的作用与它们的英文名称的含义相对应 1.ceil的英文意义是天花板,该方法就表示向上取整,所以,Math.ceil(1 ...