강력 한 기능 을 가 진 안 드 로 이 드 스 크 래 치 효과 컨트롤(ScratchView)

머리말
내 주변 에는 개발 한 일부 동료 들 이 있 는데,이런 습관 이 존재 한다.어느 날 갑자기 어떤 앱 에 예 쁜 사용자 정의 컨트롤(애니메이션)효과 가 있 는 것 을 보고 머리 를 쥐 어 짜 서 자신 이 한 발 을 실현 하려 고 한다.물론 나 자신 도 이런 유형의 소 년 에 속 하기 때문에 어떤 효 과 를 보면 손 이 근질근질 하고 실현 방법 을 궁리 하기 어렵다.개인 적 으로 이것 은 진 보 를 추진 하 는 방법 이 라 고 생각 합 니 다.머리 를 쥐 어 짜 서 원 하 는 효 과 를 실현 할 때 안 드 로 이 드 사용자 정의 컨트롤(애니메이션)에 대한 지식 체계 에 대한 인식 이 깊 어 질 수록 오 랜 시간 이 지나 면 자신 도 각종 컨트롤(그림)효 과 를 쉽게 만 들 수 있 습 니 다.만약 어느 날,제품 동 화 는 원형(또는 어떤 App 에)을 들 고 당신 에 게 말 합 니 다."XXXX,이 효과 가 우리 가 실현 할 수 있 을 것 같 습 니까?"그리고 나 서 너 는 얼핏 보 더 니 마음속 에 대나무 가 있 었 다."농담 이 야.그리고 내 가 이 룰 수 없 는 효과 도 있어?"생각해 보니까 좀 설 레 지 않 아 요?자,이제 본론 으로 돌아 갈 뻔 했 습 니 다.이것 은 제 가 처음으로 사용자 정의 컨트롤 에 관 한 글 입 니 다.이 유형의 글 도 계속 삽입 하여 업데이트 할 것 입 니 다.여러분 들 이 좋아 하 시 기 를 바 랍 니 다.몰래 내 다음 글 은 성능 최적화 에 관 한 건어물 이다.물론 저 는 건어물 이 라 고 생각 합 니 다.그때 얼굴 을 때 리 지 않 았 으 면 좋 겠 습 니 다.하하 하!)
실현 효과
이렇게 많아

위 는 기본 적 인 실현 효과 의 일부분 일 뿐 아래 에 다른 컨트롤 이 많이 있 는 것 을 볼 수 있 습 니 다.그들 은 무엇 을 하 는 지 다음 에 모든 것 을 밝 힐 것 입 니 다.
기본 실현
일상생활 에서 우 리 는 스 크 래 치 효과 에 대해 낯 설 지 않 을 것 이다.그 원 리 는 기 존의 도안 과 문자 에 스 크 래 치 를 추가 하여 이 루어 지 는 것 이다.만약 우리 가 스 크 래 치 뒤에 숨겨 진 도안 과 문자 가 무엇 인지 보고 싶다 면 반드시 스 크 래 치 를 통 해 스 크 래 치 를 해 야 한다.이런 방법 을 알 게 되면 인 코딩 을 정리 하고 생각 을 실현 한 다음 에 즐겁게 할 수 있다.
제 가 처음에 실현 한 사 고 는 ImageView 와 TextView 를 다시 쓴 다음 에 각각 코드 로 이미지 와 문자 에 그림 을 추가 하면 효 과 를 얻 을 수 있 습 니 다.그러나 돌 이 켜 보면 옳지 않다.이런 실현 은 한계 가 크다.만약 에 이런 사고방식 으로 실현 된다 면 스 크 래 치 아래 에 그림 이나 문자 만 존재 할 수 있 고 제품 매니저 가 그림 과 문자 가 동시에 존재 하 라 고 요구한다 면?두 장의 그림 을 저장 해 달라 고요?그림 과 문자 가 동시에 존재 하고 문 자 를 그림 의 위(아래,왼쪽,오른쪽)에 놓 아야 합 니까?우 리 는 세상 에서 가장 변 덕 스 러 운 것 은 여동생 의 마음 을 제외 하고 제품 매니저 와 그들의 수요 라 는 것 을 잘 알 고 있다.그래서 다른 실현 방향 을 생각해 냈 습 니 다.View 를 직접 계승 하여 스 크 래 치 를 실현 하고 이 스 크 래 치 와 그림 과 문자 가 어떠한 의존 도 하지 않도록 합 니 다.그리고 FrameLayout 와 결합 하여 스 크 래 치 를 맨 위 에 놓 습 니 다.스 크 래 치 아래 에 얼마나 많은 그림 문 자 를 놓 고 싶 습 니까?그림 문 자 는 어떻게 배치 하 든 좋 습 니 다.여기 서 생각 이 명확 하고 즐겁게 인 코딩 을 시작 할 수 있 습 니 다.
첫 번 째 단계:스 크 래 치 효 과 를 그립 니 다.

package com.clock.scratch;

import ...;

/**
 * Created by Clock on 2016/8/26.
 */
public class ScratchView extends View {

 ...

 public ScratchView(Context context) {
  super(context);
  TypedArray typedArray = context.obtainStyledAttributes(R.styleable.ScratchView);
  init(typedArray);
 }

 ...

 private void init(TypedArray typedArray) {
  ...
  mMaskColor = typedArray.getColor(R.styleable.ScratchView_maskColor, DEFAULT_MASKER_COLOR);

  mMaskPaint = new Paint();
  mMaskPaint.setAntiAlias(true);//   
  mMaskPaint.setDither(true);//  
  setMaskColor(mMaskColor);
  ...

 }

 /**
  *       
  *
  * @param color        , :0xffff0000(      )
  */
 public void setMaskColor(int color) {
  mMaskPaint.setColor(color);
 }

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  canvas.drawBitmap(mMaskBitmap, 0, 0, mBitmapPaint);//      
 }

 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  super.onSizeChanged(w, h, oldw, oldh);
  createMasker(w, h);
 }

 /**
  *     
  *
  * @param width
  * @param height
  */
 private void createMasker(int width, int height) {
  mMaskBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  mMaskCanvas = new Canvas(mMaskBitmap);
  Rect rect = new Rect(0, 0, width, height);
  mMaskCanvas.drawRect(rect, mMaskPaint);//               Bitmap
 }

}

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="ScratchView">
  <!--     -->
  <attr name="maskColor" format="color|reference" />
 </declare-styleable>

</resources>

위의 코드 사고방식 은 다음 과 같다.
View 에 이 어 사용자 정의 컨트롤 ScratchView 를 만 들 고 init()함수 에서 각종 매개 변수 설정 을 초기 화 합 니 다.스 크 래 치 색상 등;
설정 을 편리 하 게 하기 위해 서 는 파 라 메 터 를 컨트롤 의 사용자 정의 속성 으로 추출 하고 ScratchView 류 에서 set 방법 을 제공 하여 코드 호출 을 제공 해 야 합 니 다.예 를 들 어 스 크 래 치 의 색상 속성 은 바로 maskColor 이 고 클래스 에 대응 하 는 방법 은 setMaskColor 입 니 다.
onSize Changed 에서 View 를 이용 하여 Measure 가 완료 되 었 습 니 다.View 의 너비 와 높이 를 얻 을 수 있 고 Canvas 를 사용 하여 mMaskBitmap 생 성 을 초기 화하 여 스 크 래 치 를 만 드 는 데 사용 할 수 있 습 니 다.
onDraw 에서 canvas.drawBitmap 를 이용 하여 onSizeChanged 에서 mMaskBitmap 생 성 을 초기 화하 여 인터페이스 에 표시 하고 스 크 래 치 를 생 성 합 니 다.
데모 에 다음 레이아웃 을 추가 합 니 다.효과 보기:

<FrameLayout
  android:layout_width="200dp"
  android:layout_height="200dp"
  android:layout_gravity="center_horizontal"
  android:layout_marginTop="8dp">
  <!--        -->
  <ImageView
   android:layout_width="150dp"
   android:layout_height="150dp"
   android:layout_gravity="center"
   android:src="@mipmap/lufy" />
  <!--  -->
  <com.clock.scratch.ScratchView
   android:id="@+id/scratch_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
 </FrameLayout>


이로써 우 리 는 스 크 래 치 의 실현 효 과 를 얻 었 고 xml 레이아웃 과 자바 코드 에 스 크 래 치 의 색 을 직접 설정 할 수 있 습 니 다.그러나 이 때 는 스 크 래 치 만 비어 있 을 뿐 스 크 래 치 효 과 는 이 루어 지지 않 았 고 이 어 실현 코드 를 계속 추가 했다.
STEP 2:스 크 래 치 효과 구현.

package com.clock.scratch;

import ...;

public class ScratchView extends View {

 public ScratchView(Context context) {
  super(context);
  TypedArray typedArray = context.obtainStyledAttributes(R.styleable.ScratchView);
  init(typedArray);
 }

 private void init(TypedArray typedArray) {
  mEraseSize = typedArray.getFloat(R.styleable.ScratchView_eraseSize, DEFAULT_ERASER_SIZE);
  ...

  mErasePaint = new Paint();
  mErasePaint.setAntiAlias(true);
  mErasePaint.setDither(true);
  mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));//      
  mErasePaint.setStyle(Paint.Style.STROKE);
  mErasePaint.setStrokeCap(Paint.Cap.ROUND);//      ,        
  setEraserSize(mEraseSize);

  mErasePath = new Path();

  ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
  mTouchSlop = viewConfiguration.getScaledTouchSlop();

 }

 /**
  *          (      60)
  *
  * @param eraserSize        
  */
 public void setEraserSize(float eraserSize) {
  mErasePaint.setStrokeWidth(eraserSize);
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  int action = event.getAction();
  switch (action) {
   case MotionEvent.ACTION_DOWN:
    startErase(event.getX(), event.getY());
    invalidate();
    return true;
   case MotionEvent.ACTION_MOVE:
    erase(event.getX(), event.getY());
    invalidate();
    return true;
   case MotionEvent.ACTION_UP:
    stopErase();
    invalidate();
    return true;
   default:
    break;
  }
  return super.onTouchEvent(event);
 }

 /**
  *     
  *
  * @param x
  * @param y
  */
 private void startErase(float x, float y) {
  mErasePath.reset();
  mErasePath.moveTo(x, y);
  this.mStartX = x;
  this.mStartY = y;
 }

 /**
  *   
  *
  * @param x
  * @param y
  */
 private void erase(float x, float y) {
  int dx = (int) Math.abs(x - mStartX);
  int dy = (int) Math.abs(y - mStartY);
  if (dx >= mTouchSlop || dy >= mTouchSlop) {
   this.mStartX = x;
   this.mStartY = y;

   mErasePath.lineTo(x, y);
   mMaskCanvas.drawPath(mErasePath, mErasePaint);

   mErasePath.reset();
   mErasePath.moveTo(mStartX, mStartY);
  }
 }


 /**
  *     
  */
 private void stopErase() {
  this.mStartX = 0;
  this.mStartY = 0;
  mErasePath.reset();
 }

}


<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="ScratchView">
  <!--      -->
  <attr name="eraseSize" format="float" />
 </declare-styleable>

</resources>

위의 코드 사고방식 은 다음 과 같다.
init()에서 mErasePaint 와 mErasePath 를 초기 화하 고 mErasePaint 의 Xfermode 를 Porter Duff.Mode.CLEAR 로 설정 하여 뒤에서 스 크 래 치 효 과 를 만 듭 니 다.
onTouchEvent 함 수 를 다시 쓰 고 터치 이벤트 처리 ACTIONDOWN 、 ACTION_MOVE 、 ACTION_UP 등 세 가지 이벤트 유형 을 이용 하여 mErasePath 를 이용 하여 손가락 이 미 끄 러 지 는 궤적 을 기록 한 다음 에 mMaskCanvas 로 미 끄 러 지 는 궤적 을 첫 번 째 생 성 된 mMaskBitmap 에 그립 니 다.마지막 으로 invalidate()를 호출 하여 View 의 재 그리 기 를 통 해 스 크 래 치 효 과 를 생 성 합 니 다.
미끄럼 이 너무 민감 하지 않도록 시스템 이 제공 하 는 view Configuration.getScaled TouchSlop()을 통 해 시스템 이 생각 하 는 최소 미끄럼 거 리 를 가 져 와 야 합 니 다.이 거 리 를 초과 하거나 초과 할 때 미끄럼 이 라 고 생각 합 니 다.이것 이 바로 제 가 erase()에 dx>=mTouchSlop||dy>=mTouchSlop 의 판단 입 니 다.
스 크 래 치 의 굵기 를 조절 하기 위해 서 는 앞 에 스 크 래 치 를 설정 한 색상 과 마찬가지 로 ScratchView 의 속성 erase Size 를 xml 에서 제어 합 니 다.동시에 자바 코드 에서 호출 방법 을 제공 합 니 다.
여기까지 기본 적 인 스 크 래 치 효과 가 완성 되 었 으 니 실현 효과 가 어떤 지 살 펴 보 자.

위의 두 단 계 는 기초 효 과 를 완성 할 뿐 이 니,다음 에 우 리 는 최 적 화 를 해 보 자.
효과 최적화
첫 번 째 최적화:워 터 마크 추가
많은 스 크 래 치 효과 가 스 크 래 치 에 자신의 로 고 를 추가 하여 워 터 마크 를 만 드 는 효과 가 있 습 니 다.아무튼 대충 그런 뜻 이다.아래 알 리 페 이 처럼

우 리 는 기본 적 으로 실 현 된 첫 번 째 단계 에서 스 크 래 치 함수 에 실현 코드 를 추가 하 는 동시에 사용자 정의 속성 과 set 방법 을 추가 하여 호출 할 수 있 습 니 다.

 /**
  *       
  *
  * @param resId     id,-1      
  */
 public void setWatermark(int resId) {
  if (resId == -1) {
   mWatermark = null;
  } else {
   Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);
   mWatermark = new BitmapDrawable(bitmap);
   mWatermark.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
  }
 }

 /**
  *     
  *
  * @param width
  * @param height
  */
 private void createMasker(int width, int height) {
  ...

  if (mWatermark != null) {//
   Rect bounds = new Rect(rect);
   mWatermark.setBounds(bounds);
   mWatermark.draw(mMaskCanvas);
  }
 }

실현 효 과 는 다음 과 같다.

물론 효과 적 으로 추가 할 수 있 는 것 도 많 습 니 다.예 를 들 어 알 리 페 이의 가장자리 톱니 효과 도 추가 할 수 있 습 니 다.여 기 는 어린이 신발 의 자체 뇌 동 으로 이 루어 집 니 다.
두 번 째 최적화:해당 이벤트 모니터 를 추가 하고 상용 함 수 를 보완 합 니 다.
사건 감청 하면 여기 가 추첨 이 끝 난 사건 이 라 고 생각 합 니 다.이 컨트롤 을 사용 하 는 개발 자 에 게 는 긁 힌 후에 해당 하 는 조작 을 해 야 합 니 다.예 를 들 어 사용자 가 당 첨 되 었 는 지,아니면 계속 노력 하 는 지 등 입 니 다.어떻게 스 크 래 치 완성 을 판단 합 니까?여기 서 의 실현 방향 은 스 크 래 치 mMaskBitmap 의 픽 셀 정보 값 을 비동기 로 계산 하고 투명 픽 셀 개수 가 전체 픽 셀 개수 에서 차지 하 는 비례 를 계산 하 는 것 입 니 다.이 비례 가 일정한 한도 값 을 초과 할 때 우 리 는 스 크 래 치 가 완성 되 었 다 고 생각 합 니 다.왜 일정한 한도 값 을 넘 으 면 완성 된다 고 말 해 야 합 니까?이것 은 현실 생활 에서 스 크 래 치 와 마찬가지 로 스 크 래 치 를 완전히 깨끗하게 긁 지 않 아 도 결 과 를 얻 을 수 있 습 니 다.물론 이 비율 이 얼마 인지,우 리 는 동적 설정 으로 분리 해 야 한다.모니터 인터페이스 와 모니터 를 설치 한 API 를 추가 하면 된다.실현 코드 는 대체적으로 다음 과 같다.

private void onErase() {
  int width = getWidth();
  int height = getHeight();
  new AsyncTask<Integer, Integer, Boolean>() {

   @Override
   protected Boolean doInBackground(Integer... params) {
    int width = params[0];
    int height = params[1];
    int pixels[] = new int[width * height];
    mMaskBitmap.getPixels(pixels, 0, width, 0, 0, width, height);//              ,stride              

    float erasePixelCount = 0;//       
    float totalPixelCount = width * height;//     

    for (int pos = 0; pos < totalPixelCount; pos++) {
     if (pixels[pos] == 0) {//       0
      erasePixelCount++;
     }
    }

    int percent = 0;
    if (erasePixelCount >= 0 && totalPixelCount > 0) {
     percent = Math.round(erasePixelCount * 100 / totalPixelCount);
     publishProgress(percent);
    }

    return percent >= mMaxPercent;
   }

   @Override
   protected void onProgressUpdate(Integer... values) {
    super.onProgressUpdate(values);
    mPercent = values[0];
    onPercentUpdate();
   }

   @Override
   protected void onPostExecute(Boolean result) {
    super.onPostExecute(result);
    if (result && !mIsCompleted) {//    ,     
     mIsCompleted = true;
     if (mEraseStatusListener != null) {
      mEraseStatusListener.onCompleted(ScratchView.this);
     }
    }
   }

  }.execute(width, height);
 }

 /**
  *        
  *
  * @param listener
  */
 public void setEraseStatusListener(EraseStatusListener listener) {
  this.mEraseStatusListener = listener;
 }

 /**
  *        
  */
 public static interface EraseStatusListener {

  /**
   *     
   *
   * @param percent    ,  0,    100;
   */
  public void onProgress(int percent);

  /**
   *         
   *
   * @param view
   */
  public void onCompleted(View view);
 }

최종 효과 한번 볼 게 요.

이 쯤 되면 완전한 스 크 래 치 효과 사용자 정의 컨트롤 이 완료 되 었 습 니 다.그러나 여기 서 여러분 에 게 공동으로 생각해 야 할 문제 가 하나 더 있 습 니 다.바로 스 크 래 치 의 완성 여 부 를 판단 하 는 데 있어 제 가 코드 에서 의 실현 방식 은 대량의 int 배열 을 만 들 것 입 니 다.그러면 결 과 는 바로 메모리 디 더 링 이 발생 할 것 입 니 다.

지금 은 저도 좋 은 방안 을 생각 하지 못 했 기 때문에 여러분 들 이 좋 은 생각 이 있 으 면 같이 교류 하 세 요.
총결산
처음으로 사용자 정의 컨트롤 이라는 유형의 글 을 썼 습 니 다.여러분 은 생각 을 이 루 었 는 지 모 르 시 겠 습 니까?사용자 정의 컨트롤 에 대해 서 는 글 만 보면 그 생각 을 알 수 있 고 소스 코드 와 결합 하여 디 버 깅 을 실천 하 는 동시에 글 을 보면 더욱 깊이 느 낄 수 있 습 니 다.소스 코드 가 필요 한 동 화 는https://github.com/D-clock/ScratchView에서 다운로드 할 수 있 습 니 다.이 어 사용자 정의 컨트롤(애니메이션)에 대한 글 도 많이 있 습 니 다.기대 하 세 요.
작가 님 의 공유 에 감 사 드 립 니 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기