Android 如何確定貝塞爾曲線的控制點


 

 

 控制點Control(X1 + a(X2 - X1), Y1 + a(Y2 - Y1)) 

(X1,Y1)、(X2,Y2)分別代表貝塞爾曲線的起始點和終點,a在[0,1]之間隨意取值

 

package cn.study.yusiludemo.custom.besier.control;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;

import androidx.annotation.Nullable;


/**
* 通過坐標點集合
* 如何確定貝塞爾曲線的控制點,並把折線上的點繪制在曲線上
*/
public class BezierView2 extends View{
/**畫筆**/
private Paint paint;
/**路徑path**/
private Path path;
/**貝塞爾曲線控制點1**/
Point point1 = new Point();
/**貝塞爾曲線控制點2**/
Point point2 = new Point();
/**貝塞爾曲線終點**/
Point point3 = new Point();
/**設置第一個控制點33%的距離,可隨意取值在[0,1]**/
float mFirstMultiplier = 0.3f;
/**設置第二個控制點為66%的距離**/
float mSecondMultiplier = 1 - mFirstMultiplier;
/**原始坐標點**/
private List<Point> pointList = new ArrayList<>();
/**原始坐標點調整后對應到貝賽爾曲線上的坐標點**/
private List<Point> bezierPointList = new ArrayList<>();

public BezierView2(Context context) {
super(context);
}

public BezierView2(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}

public BezierView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public void init(){
path = new Path();
paint = new Paint();
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
paint.setStyle(Style.STROKE);
// 添加第一個點,
pointList.add(new Point(218,1394));
// 添加第二個點
pointList.add(new Point(406,1494));
// 添加第三個點
pointList.add(new Point(644,1218));
// 添加第四個點
pointList.add(new Point(832,1606));
// 添加第五個點
pointList.add(new Point(1020,1218));
//初始化時和原始點一樣
Collections.copy(pointList,bezierPointList);
try {
bezierPointList = deepCopy(pointList);
}catch (IOException | ClassNotFoundException ioException){

}
}

private float mProgress;
public void setPercenter(float percenter){
if(percenter < 0.0f || percenter > 1.0f){
throw new IllegalArgumentException("setPercenter must between 0.0f and 1.0f");
}
mProgress = percenter;
invalidate();
}

public void startAnimal(){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this,"percenter",0.0f,1.0f);
objectAnimator.setDuration(3000);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
path.reset();
// 通過畫折線和貝塞爾曲線可以知道,點得位置是不一樣的。
// 畫折線
for(int k = 0 ;k<pointList.size();k++){
paint.setColor(Color.GREEN);
if(k < pointList.size() - 1){
canvas.drawLine(pointList.get(k).getX(), pointList.get(k).getY(), pointList.get(k + 1 ).getX(), pointList.get(k + 1).getY(), paint);
}
paint.setColor(Color.BLUE);
canvas.drawCircle(pointList.get(k).getX(), pointList.get(k).getY(), 5, paint);
}
// 設置第一個點開始
path.moveTo(pointList.get(0).getX(), pointList.get(0).getY());
for (int b = 0; b < pointList.size(); b++) {
int nextIndex = b + 1 < pointList.size() ? b + 1 : b;
int nextNextIndex = b + 2 < pointList.size() ? b + 2 : nextIndex;
Log.e("控制點坐標===>>>",nextIndex+"==="+nextNextIndex);
// 設置第一個控制點
control(pointList, point1, b, nextIndex, mSecondMultiplier);
paint.setColor(Color.parseColor("#FF681C"));
canvas.drawCircle(point1.getX(), point1.getY(), 5, paint);
// Log.e("控制點坐標==1=>>>",p1.getX() + "===" + p1.getY());
// 設置第二個控制點
point2.setX(pointList.get(nextIndex).getX());
point2.setY(pointList.get(nextIndex).getY());
canvas.drawCircle(point2.getX(), point2.getY(), 5, paint);
// Log.e("控制點坐標==2=>>>",p2.getX() + "===" + p2.getY());
// 設置第二個控制點
control(pointList, point3, nextIndex, nextNextIndex, mFirstMultiplier);
canvas.drawCircle(point3.getX(), point3.getY(), 5, paint);
// Log.e("控制點坐標==3=>>>",p3.getX() + "===" + p3.getY());
// 最后一個點就是賽貝爾曲線上的點
path.cubicTo(point1.getX(), point1.getY(), point2.getX(), point2.getY(), point3.getX(), point3.getY());
// 畫點
}
paint.setColor(Color.RED);
PathMeasure mPathMeasure = new PathMeasure(path, false);
reSetPointWithPath(mPathMeasure, bezierPointList);
for (int k = 0; k < bezierPointList.size(); k ++) {
canvas.drawCircle(bezierPointList.get(k).getX(), bezierPointList.get(k).getY(), 5, paint);
}
canvas.drawPath(path, paint);
}

/**
* 計算控制點
* @param points
* @param result
* @param index1
* @param index2
* @param multiplier
*/
private void control(List<Point> points, Point result, int index1, int index2, final float multiplier) {
float p1x = points.get(index1).getX();
float p1y = points.get(index1).getY();
float p2x = points.get(index2).getX();
float p2y = points.get(index2).getY();

float diffX = p2x - p1x;
float diffY = p2y - p1y;
result.setX(p1x + (diffX * multiplier));
result.setY(p1y + (diffY * multiplier));
}

/**
* 重新設置數據點的位置,為曲線上的位置
* @param mPathMeasure
* @param pointsList
*/
public void reSetPointWithPath(PathMeasure mPathMeasure, List<Point> pointsList){
//Path中的點的個數
int length = (int) mPathMeasure.getLength();
int pointsLength = pointsList.size();
float[] coords = new float[2];
for (int b = 0; b < length; b++) {
//返回b點的坐標,並把x,y坐標值賦值到coords;
mPathMeasure.getPosTan(b, coords, null);
double prevDiff = Double.MAX_VALUE;
boolean ok = true;
for (int j = 0; j < pointsLength && ok; j ++) {
double diff = Math.abs(pointsList.get(j).getX() - coords[0]);
if (diff < 1) {
pointsList.get(j).setY(coords[1]);
prevDiff = diff;
}
ok = prevDiff > diff;
}
}
}

/**
* 深度復制list對象,先序列化對象,再反序列化對象
*
* @param src 需要復制的對象列表
* @return 返回新的對象列表
* @throws IOException 讀取Object流信息失敗
* @throws ClassNotFoundException 泛型類不存在
*/
public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException{
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(src);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
return (List<T>)in.readObject();
}

}
package cn.study.yusiludemo.custom.besier.control;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.List;

/**
* 通過坐標集合
* 如何確定貝塞爾曲線的控制點,並把折線上的點繪制在曲線上
*/
public class BezierView extends View{

public BezierView(Context context) {
super(context);
}

public BezierView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

public BezierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
List<Float> points = new ArrayList<Float>();

Paint paint = new Paint();
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
paint.setStyle(Style.STROKE);

// 添加第一個點,
points.add((float) 218.0);// X軸
points.add((float) 1394.0);// Y軸
// 添加第二個點
points.add((float) 406.0);
points.add((float) 1494.0);
// 添加第三個點
points.add((float) 644.0);
points.add((float) 1218.0);
// 添加第四個點
points.add((float) 832.0);
points.add((float) 1606.0);

points.add((float) 1020.0);
points.add((float) 1218.0);
// 通過畫折線和貝塞爾曲線可以知道,點得位置是不一樣的。
// 畫折線
for (int i = 0; i < points.size() - 2; i = i + 2) {
paint.setColor(Color.GREEN);
canvas.drawLine(points.get(i), points.get(i + 1), points.get(i + 2), points.get(i + 3), paint);
paint.setColor(Color.BLUE);
canvas.drawCircle(points.get(i), points.get(i + 1), 5, paint);
}
canvas.drawCircle(points.get(points.size() - 2), points.get(points.size() - 1), 5, paint);
// 貝塞爾曲線
Path p = new Path();
Point p1 = new Point();
Point p2 = new Point();
Point p3 = new Point();
float xp = points.get(0);
float yp = points.get(1);
// // 設置第一個點開始
p.moveTo(xp, yp);
int length = points.size();
// 設置第一個控制點33%的距離
float mFirstMultiplier = 0.3f;
// 設置第二個控制點為66%的距離
float mSecondMultiplier = 1 - mFirstMultiplier;
for (int b = 0; b < length; b += 2) {
int nextIndex = b + 2 < length ? b + 2 : b;
int nextNextIndex = b + 4 < length ? b + 4 : nextIndex;
Log.e("控制點坐標===>>>",nextIndex+"==="+nextNextIndex);
// 設置第一個控制點
control(points, p1, b, nextIndex, mSecondMultiplier);
paint.setColor(Color.parseColor("#FF681C"));
canvas.drawCircle(p1.getX(), p1.getY(), 5, paint);
// Log.e("控制點坐標==1=>>>",p1.getX() + "===" + p1.getY());
// 設置第二個控制點
p2.setX(points.get(nextIndex));
p2.setY(points.get(nextIndex + 1));
canvas.drawCircle(p2.getX(), p2.getY(), 5, paint);
// Log.e("控制點坐標==2=>>>",p2.getX() + "===" + p2.getY());
// 設置第二個控制點
control(points, p3, nextIndex, nextNextIndex, mFirstMultiplier);
canvas.drawCircle(p3.getX(), p3.getY(), 5, paint);
// Log.e("控制點坐標==3=>>>",p3.getX() + "===" + p3.getY());
// 最后一個點就是賽貝爾曲線上的點
p.cubicTo(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(), p3.getY());
// 畫點
}
paint.setColor(Color.RED);
PathMeasure mPathMeasure;
mPathMeasure = new PathMeasure(p, false);
reSetPointWithPath(mPathMeasure, points);
for (int k = 0; k < points.size()-1; k +=2) {
canvas.drawCircle(points.get(k), points.get(k+1), 5, paint);
}
canvas.drawPath(p, paint);
}

/**
* 計算控制點
* @param points
* @param result
* @param index1
* @param index2
* @param multiplier
*/
private void control(List<Float> points, Point result, int index1, int index2, final float multiplier) {
float p1x = points.get(index1);
float p1y = points.get(index1 + 1);
float p2x = points.get(index2);
float p2y = points.get(index2 + 1);

float diffX = p2x - p1x;
float diffY = p2y - p1y;
result.setX(p1x + (diffX * multiplier));
result.setY(p1y + (diffY * multiplier));
}

/**
* 重新設置數據點的位置,為曲線上的位置
* @param mPathMeasure
* @param pointsList
*/
public void reSetPointWithPath(PathMeasure mPathMeasure, List<Float> pointsList){
int length = (int) mPathMeasure.getLength();
int pointsLength = pointsList.size();
float[] coords = new float[2];
for (int b = 0; b < length; b++) {
//返回b點的坐標,並把x,y坐標值賦值到coords;
mPathMeasure.getPosTan(b, coords, null);
double prevDiff = Double.MAX_VALUE;
boolean ok = true;
for (int j = 0; j < pointsLength && ok; j += 2) {
double diff = Math.abs(pointsList.get(j) - coords[0]);
if (diff < 1) {
pointsList.set(j + 1, coords[1]);
prevDiff = diff;
}
ok = prevDiff > diff;
}
}
}

}




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM