1、關於貝塞爾曲線算法
在工業設計方面貝塞爾曲線有不少用途,一樣,在Android中,貝塞爾曲線結合Path類能夠實現更復雜的圖形,這裏咱們給一個案例,來實現一種旋轉的花朵。對於貝賽爾曲線的理解,建議參考《Android高級繪製——繪圖篇(三)路徑Path繪製以及貝塞爾曲線使用技巧》,寫的很是詳細。canvas
2、自定義View的誤區app
今天咱們自定義的View效果以下:ide
對於花朵而言,首先要構建花瓣,花瓣這裏使用了三階貝賽爾曲線,由於二階貝賽爾曲線畫出來的是樹葉。函數
private void buildLeaf(Canvas canvas){ mPaint.setColor(0xff40835e); int width = getWidth(); int height = getHeight(); if(width==0 || height==0){ return; } int centerX = width/2; int centerY = height/2; int Radius = Math.max(width,height)/2; Path path = new Path(); path.moveTo(0,0); // float leftctrY = - (Radius*4)/5.0f; float leftctrY = - (Radius*7)/10.0f; float leftctrX = -(float) (Math.abs(leftctrY) * Math.tan(Math.toRadians(30))); // path.lineTo(leftctrX,leftctrY); int lastX = 0; int lastY = -Radius; float rightctrY = - (Radius*7)/10.0f; float rightctrX = (float) (Math.abs(rightctrY) * Math.tan(Math.toRadians(30))); path.quadTo(leftctrX,leftctrY,lastX,lastY); path.quadTo(rightctrX,rightctrY,0,0); path.close(); path.setFillType(Path.FillType.WINDING); int restoreId = canvas.save(); Paint.Style style = mPaint.getStyle(); // mPaint.setStyle(Paint.Style.STROKE); canvas.translate(centerX,centerY); canvas.drawPath(path,mPaint); mPaint.setStyle(style); canvas.restoreToCount(restoreId); }
而咱們須要的帶有弧度的花瓣,所以二階顯然不行,花瓣的畫法難度主要集中於三角函數的計算,此外還有第二個控制點的肯定,第二個控制點與原點的舉例實際上和離原點最遠的邊平行,此外過最遠的點做垂線相交,不然可能產生以下效果。ui
private void buildHeart(Canvas canvas){ mPaint.setColor(0xffa7324a); int width = getWidth(); int height = getHeight(); if(width==0 || height==0){ return; } int centerX = width/2; int centerY = height/2; int Radius = Math.max(width,height)/2; Path path = new Path(); path.moveTo(0,0); // float leftctrY = - (Radius*4)/5.0f; float leftctrY = - (Radius*5)/10.0f; float leftctrX = -(float) (Math.abs(leftctrY) * Math.tan(Math.toRadians(60))); // path.lineTo(leftctrX,leftctrY); int lastX = 0; float lastY = -Radius * 8f/10; float rightctrY = - (Radius*5)/10.0f; float rightctrX = (float) (Math.abs(rightctrY) * Math.tan(Math.toRadians(60))); path.cubicTo(leftctrX,leftctrY,leftctrX,-Radius,lastX,lastY); path.cubicTo(rightctrX,-Radius,rightctrX,rightctrY,0,0); path.close(); path.setFillType(Path.FillType.WINDING); int restoreId = canvas.save(); Paint.Style style = mPaint.getStyle(); canvas.translate(centerX,centerY); canvas.drawPath(path,mPaint); mPaint.setStyle(style); canvas.restoreToCount(restoreId); }
3、實現自定義View-旋轉的花朵this
public class FlowerView extends View { private TextPaint mPaint; private int strokeWidth = 1; private int textSize = 12; private int minContentSize = 0; private Path mPath; private int mPetalNumbers = 7; private float degree; private int defaultPetalColor = 0xFFFF1493; private int[] colorSet = new int[]{0xffFF1493,0xffFFD700,0xffFFFF00,0xff87CEFA,0xff00FA9A,0xffBA55D3,0xffE0FFFF}; private boolean isPlaying = false; public FlowerView(Context context) { this(context,null); } public FlowerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public FlowerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setClickable(true); initPaint(); minContentSize = ViewConfiguration.get(context).getScaledTouchSlop() * 2; mPath = new Path(); } public boolean isPlaying() { return isPlaying; } private void initPaint() { // 實例化畫筆並打開抗鋸齒 mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG ); mPaint.setAntiAlias(true); mPaint.setPathEffect(new CornerPathEffect(10)); //設置線條類型 mPaint.setStrokeWidth(dip2px(strokeWidth)); mPaint.setTextSize(dip2px((textSize))); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); if(widthMode!=MeasureSpec.EXACTLY){ width = (int) dip2px(210); } if(heightMode!=MeasureSpec.EXACTLY){ height = (int) dip2px(210); } setMeasuredDimension(width,height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int width = getWidth(); final int height = getHeight(); if(width<minContentSize || height<minContentSize) return; int contentSize = Math.min(width,height); //取最小邊長,防止畫出邊界 clearCanvas(canvas); canvas.drawColor(0xffffffff); int centerX = width/2; int centerY = height/2; final int restoreId = canvas.save(); canvas.translate(centerX,centerY); //將座標系移到中心 mPaint.setStyle(Paint.Style.STROKE); drawFlower(canvas,contentSize); canvas.restoreToCount(restoreId); } public void setPetalNumber(int num,int[] colorSet){ this.mPetalNumbers = num; this.colorSet = colorSet; invalidate(); } ValueAnimator animator = null; public void startRotate(){ stopRotate(); isPlaying = true; if(animator==null) { animator = ValueAnimator.ofFloat(0, 360); animator.setEvaluator(new TypeEvaluator<Float>() { @Override public Float evaluate(float fraction, Float startValue, Float endValue) { return new Float(fraction * 360); } }); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.setDuration(3000) .setRepeatMode(ValueAnimator.RESTART); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { setDegree((Float) animation.getAnimatedValue()); } }); } animator.start(); } public void stopRotate(){ isPlaying = false; if(animator!=null){ animator.cancel(); animator = null; } } private void drawFlower(Canvas canvas, int contentSize) { int N = this.mPetalNumbers; for (int i=0;i< N;i++){ drawFlowerPath(canvas,contentSize,N,i); } } //花瓣算法 private void drawFlowerPath(Canvas canvas, int contentSize,int N,int pos) { float perDegree = 360f/N; final float R = contentSize/2f; final float degree = perDegree*pos + this.degree; float endY = (float) (R * Math.sin(Math.toRadians(degree))); float endX = (float) (R * Math.cos(Math.toRadians(degree)));; float firstCtlLength = (float) ((R / 2f) / Math.cos(Math.PI / N)); float leftY = (float) ((firstCtlLength) * Math.sin(degree* Math.PI/180 - Math.PI/N)); float leftX = (float) ((firstCtlLength) * Math.cos(degree* Math.PI/180 - Math.PI/N)); float rightY = (float) ((firstCtlLength) * Math.sin(degree* Math.PI/180 + Math.PI/N)); float rightX = (float) ((firstCtlLength) * Math.cos(degree* Math.PI/180 + Math.PI/N)); float topLeftY = leftY + (float) ( R/2f * Math.sin(degree* Math.PI/180)); //左側第二控制點 float topLeftX = leftX + (float)( R/2f * Math.cos(degree* Math.PI/180)) ; float topRightY = rightY + (float) ( R/2f * Math.sin(degree* Math.PI/180)); //右側第二控制點 float topRightX = rightX + (float)( R/2f * Math.cos(degree* Math.PI/180)) ; mPath.reset(); mPath.moveTo(0,0); mPath.cubicTo(leftX,leftY,topLeftX,topLeftY,endX,endY); mPath.cubicTo(topRightX,topRightY,rightX,rightY,0,0); mPath.close(); if(colorSet==null || colorSet.length==0) { mPaint.setColor(0x9aFB2222); }else{ mPaint.setColor(colorSet[pos%N]); } mPath.setFillType(Path.FillType.WINDING); mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(mPath,mPaint); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); canvas.drawPath(mPath,mPaint); } private synchronized void clearCanvas(Canvas canvas) { final Xfermode xfermode = mPaint.getXfermode(); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); canvas.drawPaint(mPaint); mPaint.setXfermode(xfermode); } public float dip2px(int dp){ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,getResources().getDisplayMetrics()); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); } public void setDegree(float degree) { this.degree = degree; invalidate(); } }
以上就是整個View的實現,使用方法以下lua
myFlower = (FlowerView) findViewById(R.id.id_myflower); myFlower.setPetalNumber(7,null); myFlower.setOnClickListener(this); //省略其餘不須要給大家看的代碼 @Override public void onClick(View v) { if(v.getId()==R.id.id_myflower){ myFlower.startRotate(); } }