Android Path 베 어 셀 곡선 그리 기 QQ 드래그 거품 실현
최종 효과 도
앞으로 한 걸음 한 걸음 전체 과정 을 실현 하 다.
기본 원리
사실은 Path 로 세 점 의 2 차방 베 세 르 곡선 을 그 려 서 그 매혹 적 인 곡선 을 완성 한 것 이다.그리고 터치 점 에 따라 대응 하 는 원형 을 계속 그리고 거리의 변화 에 따라 원시 고정 원형 의 반지름 크기 를 바꾼다.마지막 으로 손 을 놓 고 돌아 오 거나 터 지 는 실현 이다.
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 차 곡선의 닫 힌 Path 경 로 를 그린 다음 에 그 위 에 두 개의 원형 을 추가 하 는 것 이다.
닫 힌 Path 경 로 는 왼쪽 위 에서 두 번 베 세 르 곡선 을 왼쪽 아래 점 까지 그리고 왼쪽 아래 점 에서 오른쪽 아래 점 까지 연결 하 며 오른쪽 아래 점 에서 두 번 베 세 르 곡선 에서 오른쪽 위 점 까지 마지막 으로 닫 습 니 다!!
관련 좌표 의 확정
이것 은 이번 안의 어 려 운 점 중 하나 입 니 다.수학 중의 sin,cos,tan 등 과 관련 되 었 기 때문에 저도 사실 다 잊 었 습 니 다.그리고 머리 를 써 서 보충 을 했 습 니 다.쓸데없는 말 은 하지 않 겠 습 니 다.
왜 직접 그 려 보 세 요?360 회전 과정 에서 각 표 체 계 는 두 세트 가 있 습 니 다.한 세트 로 그 리 려 면 회전 과정 에서 곡선 이 겹 치 는 상황 을 그 려 야 합 니 다!
문 제 는 이미 던 져 졌 으 니,이어서 코드 의 실현 을 직접 보 자!
각도 확정
붙 인 원리 도 에 따 르 면 우 리 는 시작 원심 좌표 와 끌 어 당 기 는 원심 좌 표를 사용 하여 어차피 절 함수 에 따라 구체 적 인 호 도 를 얻 을 수 있다.
int dy = Math.abs(CIRCLEY - startY);
int dx = Math.abs(CIRCLEX - startX);
angle = Math.atan(dy * 1.0 / dx);
ok,여기 startX,Y 는 이동 과정 에서 의 좌표 입 니 다.angle 은 대응 하 는 라디안(각도)을 얻 는 것 이다.관련 경로 그리 기
앞에서 말 했 듯 이 회전 하 는 과정 에서 두 개의 좌표 체계 가 있다.처음에 나 도 이 좌표 체 계 를 어떻게 확정 해 야 할 지 고민 했다.그 다음 에 문득 깨 달 았 다.사실은 13 상한 의 정비례 성장,2,4 상한,반 비례 성장 에 해당 한다.
flag = (startY - CIRCLEY ) * (startX- CIRCLEX ) <= 0;
//어떤 좌표 체 계 를 사용 하 는 지 판단 하기 위해 flag 를 추가 합 니 다.
가장 중요 한 것 이 왔 습 니 다.관련 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);
}
여기 코드 는 그림 에 있 는 수학 공식 을 자바 화 하 는 것 뿐 입 니 다!여기까지,사실 주요 한 일 은 거의 완성 되 지 않 았 다!
다음은 paint 를 충전 효과 로 설정 하고 마지막 으로 원 두 개 를 그립 니 다.
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;
사건 이 나 눠 준 구 덩이 를 처리 하 라!측량 과 배치
이렇게 하면 기본적으로 지나 갈 수 있 지만,우리 의 배치 따 위 는 아직 처리 되 지 않 았 다.mathparent 는 구체 적 인 항목 에 사용 할 수 없습니다!
측정 할 때 정확 한 패턴 이 아 닌 것 을 발견 하면 필요 한 너비 와 높이 를 수 동 으로 계산한다.
@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 가 그 릴 수 있 는 구역 은 자신 만 의 크기 로 끌 어 당 겨 도 보이 지 않 습 니 다!이 구 덩이 는 어떻게 합 니까?사실은 매우 간단 합 니 다.아버지 구조 에 안 드 로 이 드:clipChildren="false"의 속성 을 더 합 니 다!이 구덩이 도 해 결 된 셈 이 야!!
관련 상태의 확정
우 리 는 그것 이 무한 하 게 끌 어 당 길 수 있 기 를 바라 지 않 는 다.바로 끌 어 당 기 는 가장 먼 거리 가 있 고,또 손 을 놓 은 후에 돌아 와 폭발 하 는 것 이다.그러면 대응 하 는 것 은 몇 가지 상 태 를 확인 해 야 합 니 다.
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);
}
updatePath()의 방법 은 이전에 이미 부분 을 보 았 는데,이번 에는 완전 하 다.여기 서 하 는 일 은 끌 어 당 기 는 거리 에 따라 관련 상 태 를 바 꾸 고 백분율 에 따라 원시 원형의 반지름 크기 를 바 꾸 는 것 이다.그리고 아까 소 개 했 던 라디안 확인!
마지막 으로 놓 을 때:
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;
여기에 사 용 된 ValueAnimator 를 자동 으로 되 돌려 줍 니 다.
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();
}
});
마지막 으로 완전한 온 드 로 방법 을 보 겠 습 니 다!
@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 경 로 를 가지 지 않 고 바로 돌아 오 거나 폭발 합 니 다!
이상 은 안 드 로 이 드 Path 로 베 어 셀 곡선 을 그 리 는 예제 입 니 다.추 후 관련 글 을 계속 보충 하 겠 습 니 다.본 사이트 에 대한 지지 에 감 사 드 립 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.