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

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

先看效果吧:

4d797a54a2440b049c038132bde8705b.gif

然后我们来说说基本的做法:

•根据画笔宽度,计算每一条线段两个顶点对应的四个点,四点连线,包围线段,形成一个路径。

•后一条线段的路径的前两个点,取(等于)前一条线段的后两点,这样就衔接起来了。

把Path的Style修改为FILL,效果是这样的:

172f0de705dfa9a049c8f424e0e7d011.gif

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

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

先看一张图:

c83ec4f4ab31474833c9e98ebfb4c740.png

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

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

y = k*x + b

黑线的斜率是:

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

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

k1 * k2 = -1

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

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

e07351cc834142824a2ed1f4e45b1e80.png

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

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

9281fd5b6ec18075468fcea9d4ceeed7.png

好啦,最后,上代码:

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 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() ;

}

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);

}

}

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值