Android Path 베 어 셀 곡선 그리 기 QQ 드래그 거품 실현

이틀 동안 Path 를 사용 하여 베 세 르 곡선 을 그 리 는 것 을 배 웠 고 QQ 읽 지 않 은 메시지 와 같은 작은 기 포 를 직접 만 들 었 습 니 다.효과 도 는 다음 과 같 습 니 다.

최종 효과 도
앞으로 한 걸음 한 걸음 전체 과정 을 실현 하 다.
기본 원리
사실은 Path 로 세 점 의 2 차방 베 세 르 곡선 을 그 려 서 그 매혹 적 인 곡선 을 완성 한 것 이다.그리고 터치 점 에 따라 대응 하 는 원형 을 계속 그리고 거리의 변화 에 따라 원시 고정 원형 의 반지름 크기 를 바꾼다.마지막 으로 손 을 놓 고 돌아 오 거나 터 지 는 실현 이다.
Path 소개:
말 그대로 하나의 경로 라 는 뜻 입 니 다.Path 에는 여러 가지 방법 이 있 는데 이번 디자인 에 주로 사용 되 는 방법 은
  • moveto()지정 한 지점 으로 Path 이동
  • quad To()는 두 번 째 베 세 르 곡선 을 그립 니 다.두 번 째 점 을 받 습 니 다.첫 번 째 는 라디안 을 제어 하 는 점 이 고 두 번 째 는 종점 입 니 다.
  • lineTo()가 바로 연결선
  • close()닫 기 Path 경로,
  • reset()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 로 베 어 셀 곡선 을 그 리 는 예제 입 니 다.추 후 관련 글 을 계속 보충 하 겠 습 니 다.본 사이트 에 대한 지지 에 감 사 드 립 니 다!

    좋은 웹페이지 즐겨찾기