Android Pathは、ベジェ曲線を描いてQQの泡を引っ張ることを実現します。
この二日間、Pathを使ってベジェ曲線を描くことについて勉強しました。そして自分でQQ未読メッセージのようなプチバブルを作りました。効果図は以下の通りです。
最終効果図
次のステップはプロセス全体を実現します。
基本原理
つまり、Pathを使って三点の二次曲線を描いて、そのグラマー曲線を完成させたのです。そして、タッチポイントに応じて円を描き続け、距離に応じて元の固定円の半径の大きさを変えます。最後に手を離して戻ったり、ひびが入ったりします。
Path紹介:
その名の通り、パスという意味です。Pathには多くの方法があります。今回のデザインは主に使う方法があります。 moveTo()指定されたポイント にPathを移動します。 quadTo()は二次ベジェ曲線を描き、二つの点を受け、一つ目はラジアンを制御する点で、二つ目は終点です。 ラインToは接続ライン です。 close()は、Pathパスを閉じ、 リセットされたPathの関連設定
Path入門ウォームアップ:
Path
分割を具体的に行う:
実際には、2つのベジェ二次曲線の閉じたパスを描いて、上に2つの円を追加します。
閉じたPathパスは、左上の点から二次曲線を左の下に、左下の点から右下に、右下の二次曲線を右上に、最後に閉じます。
関連座標の決定
これは今回の中の難点の一つです。数学の中の一つのsin、cos、tanなどに関連していますので、私も忘れてしまいました。それから、また頭を埋めました。無駄話は多く言いません。
なぜ自分で描きに行くのですか?描いてみて、360を回る過程で、角標システムは二つのセットがあります。一つのセットを使って描いたら、回転の過程で曲線が重なっていることが分かります。
問題はもう投げました。これから直接コードを見て実現します。
角度の決定
貼り付けられた原理図によれば、スタート円心座標とドラッグの円心座標を使って、逆切削関数によって特定の円弧を得ることができます。
関連するPathの描画
前に述べたように、回転の過程では二つの座標体系があります。最初はこの座標体系をどうやって確定するかにも拘りました。後にははっと悟りました。実は一三象限の正比例成長、二四象限、反比例成長に相当します。
フラグ=(startY-CIRCLEY) ) * (startX-CIRCLEX)<=0;
//どの座標系を使うかを判断するためにflagsを追加します。
最も重要なのは来て、関連しているPathパスを描画します!
ここに来て、主要な仕事はほぼ完成しました。
次に、paintを充填効果に設定し、最後に2つの円を描きます。
ここでもう一回ontouchの処理を言わなければなりません。
測定とレイアウト
これは基本的にはできますが、私たちのレイアウトなどはまだ処理されていません。parentは具体的なプロジェクトには絶対使えません。
測定時、精確モードではないことが判明したら、必要な幅と高さを手動で計算します。
この坑も解決しました。
関連状態の確定
私達はそれが無限に引っ張ることができることを望まないので、1つの引っ張っている最も遠い距離があって、あります。それでは、いくつかの状態を確認する必要があります。
ここですることは、ドラッグした距離によって関連した状態を変更し、パーセントによって元の円形の半径の大きさを変更します。また、前に紹介したのは、確かな弧度です。
最後に手を放す時:
まとめ:
1、デフォルトの円形の座標を決定します。
2、moveの状況に応じて、最新の座標をリアルタイムで取得し、移動距離(角度を確定)に応じて、関連する状態を更新し、関連するPathパスを描く。上限を超えて、Pathパスを描きません。
3、手を放す時、関連している状態によって、あるいはPathパスを持ってアニメーションを実行して戻ってくるか、あるいはPathパスを持たずに直接に戻ってくるか、あるいは直接バーストするか!
以上はAndroid Pathでベジェ曲線を描いた例です。引き続き関連記事を補充します。ありがとうございます。
最終効果図
次のステップはプロセス全体を実現します。
基本原理
つまり、Pathを使って三点の二次曲線を描いて、そのグラマー曲線を完成させたのです。そして、タッチポイントに応じて円を描き続け、距離に応じて元の固定円の半径の大きさを変えます。最後に手を離して戻ったり、ひびが入ったりします。
Path紹介:
その名の通り、パスという意味です。Pathには多くの方法があります。今回のデザインは主に使う方法があります。
path.reset();
path.moveTo(200, 200);
// ,
path.quadTo(400, 250, 600, 200);
canvas.drawPath(path, paint);
canvas.translate(0, 200);
// close,
path.close();
canvas.drawPath(path, paint);
onDrawメソッドでnew PathやPaintはやめてください。Path
分割を具体的に行う:
実際には、2つのベジェ二次曲線の閉じたパスを描いて、上に2つの円を追加します。
閉じたPathパスは、左上の点から二次曲線を左の下に、左下の点から右下に、右下の二次曲線を右上に、最後に閉じます。
関連座標の決定
これは今回の中の難点の一つです。数学の中の一つのsin、cos、tanなどに関連していますので、私も忘れてしまいました。それから、また頭を埋めました。無駄話は多く言いません。
なぜ自分で描きに行くのですか?描いてみて、360を回る過程で、角標システムは二つのセットがあります。一つのセットを使って描いたら、回転の過程で曲線が重なっていることが分かります。
問題はもう投げました。これから直接コードを見て実現します。
角度の決定
貼り付けられた原理図によれば、スタート円心座標とドラッグの円心座標を使って、逆切削関数によって特定の円弧を得ることができます。
int dy = Math.abs(CIRCLEY - startY);
int dx = Math.abs(CIRCLEX - startX);
angle = Math.atan(dy * 1.0 / dx);
ここのstartX、Yは移動中の座標です。アングルとは、対応する弧度(角度)を得ることです。関連するPathの描画
前に述べたように、回転の過程では二つの座標体系があります。最初はこの座標体系をどうやって確定するかにも拘りました。後にははっと悟りました。実は一三象限の正比例成長、二四象限、反比例成長に相当します。
フラグ=(startY-CIRCLEY) ) * (startX-CIRCLEX)<=0;
//どの座標系を使うかを判断するためにflagsを追加します。
最も重要なのは来て、関連しているPathパスを描画します!
path.reset();
if (flag) {
//
path.moveTo((float) (CIRCLEX - Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY - Math.cos(angle) * ORIGIN_RADIO));
path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (startX - Math.sin(angle) * DRAG_RADIO), (float) (startY - Math.cos(angle) * DRAG_RADIO));
path.lineTo((float) (startX + Math.sin(angle) * DRAG_RADIO), (float) (startY + Math.cos(angle) * DRAG_RADIO));
path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (CIRCLEX + Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY + Math.cos(angle) * ORIGIN_RADIO));
path.close();
canvas.drawPath(path, paint);
} else {
//
path.moveTo((float) (CIRCLEX - Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY + Math.cos(angle) * ORIGIN_RADIO));
path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (startX - Math.sin(angle) * DRAG_RADIO), (float) (startY + Math.cos(angle) * DRAG_RADIO));
path.lineTo((float) (startX + Math.sin(angle) * DRAG_RADIO), (float) (startY - Math.cos(angle) * DRAG_RADIO));
path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (CIRCLEX + Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY - Math.cos(angle) * ORIGIN_RADIO));
path.close();
canvas.drawPath(path, paint);
}
ここのコードは写真に関する数学公式Java化です。ここに来て、主要な仕事はほぼ完成しました。
次に、paintを充填効果に設定し、最後に2つの円を描きます。
paint.setStyle(Paint.Style.FILL)
canvas.drawCircle(CIRCLEX, CIRCLEY, ORIGIN_RADIO, paint);//
canvas.drawCircle(startX == 0 ? CIRCLEX : startX, startY == 0 ? CIRCLEY : startY, DRAG_RADIO, paint);//
希望の効果を描画できます。ここでもう一回ontouchの処理を言わなければなりません。
case MotionEvent.ACTION_DOWN:// !!
getParent().requestDisallowInterceptTouchEvent(true);
CurrentState = STATE_IDLE;
animSetXY.cancel();
startX = (int) ev.getX();
startY = (int) ev.getRawY();
break;
イベントの配布穴を処理してください。測定とレイアウト
これは基本的にはできますが、私たちのレイアウトなどはまだ処理されていません。parentは具体的なプロジェクトには絶対使えません。
測定時、精確モードではないことが判明したら、必要な幅と高さを手動で計算します。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
if (modeWidth == MeasureSpec.UNSPECIFIED || modeWidth == MeasureSpec.AT_MOST) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_RADIO * 2, MeasureSpec.EXACTLY);
}
if (modeHeight == MeasureSpec.UNSPECIFIED || modeHeight == MeasureSpec.AT_MOST) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_RADIO * 2, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
そして、レイアウトが変化したときに、相関座標を取得し、初期の円心座標を決定します。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
CIRCLEX = (int) ((w) * 0.5 + 0.5);
CIRCLEY = (int) ((h) * 0.5 + 0.5);
}
そしてリストファイルの中にはこのように配置できます。
<com.lovejjfg.circle.DragBubbleView
android:id="@+id/dbv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
この後、もう一つ問題があります。それはwrap_です。contentの後で、このViewの製作することができる領域は自分だけそんなに大きくなって、引っ張ってすべて見えなくなりました!このピットはどうすればいいですか?実は簡単です。父のレイアウトにandroid:clipChildren=falseの属性があります。この坑も解決しました。
関連状態の確定
私達はそれが無限に引っ張ることができることを望まないので、1つの引っ張っている最も遠い距離があって、あります。それでは、いくつかの状態を確認する必要があります。
private final static int STATE_IDLE = 1;//
private final static int STATE_DRAG_NORMAL = 2;//
private final static int STATE_DRAG_BREAK = 3;//
private final static int STATE_UP_BREAK = 4;//
private final static int STATE_UP_BACK = 5;//
private final static int STATE_UP_DRAG_BREAK_BACK = 6;//
private int CurrentState = STATE_IDLE;
private int MIN_RADIO = (int) (ORIGIN_RADIO * 0.4);//
private int MAXDISTANCE = (int) (MIN_RADIO * 13);//
これらを確認してから、moveの時に関連する判断をします。
case MotionEvent.ACTION_MOVE://
startX = (int) ev.getX();
startY = (int) ev.getY();
updatePath();
invalidate();
break;
private void updatePath() {
int dy = Math.abs(CIRCLEY - startY);
int dx = Math.abs(CIRCLEX - startX);
double dis = Math.sqrt(dy * dy + dx * dx);
if (dis <= MAXDISTANCE) {// ,
if (CurrentState == STATE_DRAG_BREAK || CurrentState == STATE_UP_DRAG_BREAK_BACK) {
CurrentState = STATE_UP_DRAG_BREAK_BACK;
} else {
CurrentState = STATE_DRAG_NORMAL;
}
ORIGIN_RADIO = (int) (DEFAULT_RADIO - (dis / MAXDISTANCE) * (DEFAULT_RADIO - MIN_RADIO));
Log.e(TAG, "distance: " + (int) ((1 - dis / MAXDISTANCE) * MIN_RADIO));
Log.i(TAG, "distance: " + ORIGIN_RADIO);
} else {
CurrentState = STATE_DRAG_BREAK;
}
// distance = dis;
flag = (startY - CIRCLEY) * (startX - CIRCLEX) <= 0;
Log.i("TAG", "updatePath: " + flag);
angle = Math.atan(dy * 1.0 / dx);
}
udatePathの方法は前にもう部分を見ました。今回は完全です。ここですることは、ドラッグした距離によって関連した状態を変更し、パーセントによって元の円形の半径の大きさを変更します。また、前に紹介したのは、確かな弧度です。
最後に手を放す時:
case MotionEvent.ACTION_UP:
if (CurrentState == STATE_DRAG_NORMAL) {
CurrentState = STATE_UP_BACK;
valueX.setIntValues(startX, CIRCLEX);
valueY.setIntValues(startY, CIRCLEY);
animSetXY.start();
} else if (CurrentState == STATE_DRAG_BREAK) {
CurrentState = STATE_UP_BREAK;
invalidate();
} else {
CurrentState = STATE_UP_DRAG_BREAK_BACK;
valueX.setIntValues(startX, CIRCLEX);
valueY.setIntValues(startY, CIRCLEY);
animSetXY.start();
}
break;
ここに自動的に戻って使うValue Animtorは、
animSetXY = new AnimatorSet();
valueX = ValueAnimator.ofInt(startX, CIRCLEX);
valueY = ValueAnimator.ofInt(startY, CIRCLEY);
animSetXY.playTogether(valueX, valueY);
valueX.setDuration(500);
valueY.setDuration(500);
valueX.setInterpolator(new OvershootInterpolator());
valueY.setInterpolator(new OvershootInterpolator());
valueX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
startX = (int) animation.getAnimatedValue();
Log.e(TAG, "onAnimationUpdate-startX: " + startX);
invalidate();
}
});
valueY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
startY = (int) animation.getAnimatedValue();
Log.e(TAG, "onAnimationUpdate-startY: " + startY);
invalidate();
}
});
最後に完全なonDraw方法を見てみましょう。
@Override
protected void onDraw(Canvas canvas) {
switch (CurrentState) {
case STATE_IDLE:// ,
if (showCircle) {
canvas.drawCircle(CIRCLEX, CIRCLEY, ORIGIN_RADIO, paint);//
}
break;
case STATE_UP_BACK://
case STATE_DRAG_NORMAL://
path.reset();
if (flag) {
//
path.moveTo((float) (CIRCLEX - Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY - Math.cos(angle) * ORIGIN_RADIO));
path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (startX - Math.sin(angle) * DRAG_RADIO), (float) (startY - Math.cos(angle) * DRAG_RADIO));
path.lineTo((float) (startX + Math.sin(angle) * DRAG_RADIO), (float) (startY + Math.cos(angle) * DRAG_RADIO));
path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (CIRCLEX + Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY + Math.cos(angle) * ORIGIN_RADIO));
path.close();
canvas.drawPath(path, paint);
} else {
//
path.moveTo((float) (CIRCLEX - Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY + Math.cos(angle) * ORIGIN_RADIO));
path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (startX - Math.sin(angle) * DRAG_RADIO), (float) (startY + Math.cos(angle) * DRAG_RADIO));
path.lineTo((float) (startX + Math.sin(angle) * DRAG_RADIO), (float) (startY - Math.cos(angle) * DRAG_RADIO));
path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (CIRCLEX + Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY - Math.cos(angle) * ORIGIN_RADIO));
path.close();
canvas.drawPath(path, paint);
}
if (showCircle) {
canvas.drawCircle(CIRCLEX, CIRCLEY, ORIGIN_RADIO, paint);//
canvas.drawCircle(startX == 0 ? CIRCLEX : startX, startY == 0 ? CIRCLEY : startY, DRAG_RADIO, paint);//
}
break;
case STATE_DRAG_BREAK:// , :
case STATE_UP_DRAG_BREAK_BACK:
if (showCircle) {
canvas.drawCircle(startX == 0 ? CIRCLEX : startX, startY == 0 ? CIRCLEY : startY, DRAG_RADIO, paint);//
}
break;
case STATE_UP_BREAK://
canvas.drawCircle(startX - 25, startY - 25, 10, circlePaint);
canvas.drawCircle(startX + 25, startY + 25, 10, circlePaint);
canvas.drawCircle(startX, startY - 25, 10, circlePaint);
canvas.drawCircle(startX, startY, 18, circlePaint);
canvas.drawCircle(startX - 25, startY, 10, circlePaint);
break;
}
}
ここに来たら完成品が出ます。まとめ:
1、デフォルトの円形の座標を決定します。
2、moveの状況に応じて、最新の座標をリアルタイムで取得し、移動距離(角度を確定)に応じて、関連する状態を更新し、関連するPathパスを描く。上限を超えて、Pathパスを描きません。
3、手を放す時、関連している状態によって、あるいはPathパスを持ってアニメーションを実行して戻ってくるか、あるいはPathパスを持たずに直接に戻ってくるか、あるいは直接バーストするか!
以上はAndroid Pathでベジェ曲線を描いた例です。引き続き関連記事を補充します。ありがとうございます。