Android 사용자 정의 View QQ 토론 그룹 이미지 모방 효과

먼저 우리 가 모방 한 효과 도 를 살 펴 보면 QQ 를 사용 한 사람 에 게 낯 설 지 않 을 것 이 라 고 믿 습 니 다.효과 도 는 다음 과 같 습 니 다.

이전 프로젝트 에 서 는 QQ 토론 팀 의 프로필 사진 과 유사 한 컨트롤 이 필요 합 니 다.다만 프로필 사진 의 수량 과 레이아웃 이 조금 다 릅 니 다.하 나 는 가장 프로필 사진 수가 4 개 이 고,다른 하 나 는 프로필 사진 수가 2 개 일 때 레이아웃 이 가로로 배열 되 어 있 습 니 다.사실 그 당시 GitHub 에 비슷 한 오픈 소스 컨트롤 이 있 었 습 니 다.다만 그 컨트롤 은 View 를 그 릴 때마다 Bitmap 대상 을 새로 만 들 었 습 니 다.이것 은 바람 직 하지 않 을 것 입 니 다.그리고 그 컨트롤 이미지 가 Bitmap 대상 을 입력 하여 수 요 를 만족 시 키 지 못 했 습 니 다.그래서 스스로 하 나 를 이 룰 수 밖 에 없 었 다.실현 할 때 도 크게 고려 하지 않 았 다.이미지 Drawable 대상 을 입력 하고 수량 에 따라 배열 하여 표시 하면 완성 되 며 들 어 오 는 이미 지 는 원형 이 어야 하 며 제한 이 많아 유 니 버 설 성 이 전혀 없다.따라서 QQ 토론 팀 의 얼굴 과 같은 유 니 버 설 컨트롤 을 실현 하려 면 재 설계,실현 이 필요 하 다.
다음은 우리 가 실현 하기 시작 합 시다.
배치
먼저 해결 해 야 할 것 은 두상 의 레이아웃 이다.두상 의 수량 이 각각 1 에서 5 인 상황 에서 두상 의 레이아웃 배열 방식 을 정의 하고 이미지 의 크기 와 위 치 를 계산한다.먼저 배치 도 를 그 려 서 다시 이야기 하 자.

배치
그 중에서 검은색 정사각형 은 바로 View 의 디 스 플레이 구역 이 고 파란색 원형 은 두상 이다.이미 알 고 있 는 조건 은 View 크기 입 니 다.우선 D 로 설정 하 세 요.그리고 두상 의 수량 n 이 있 습 니 다.파란색 원 의 반지름 r 와 원심 위 치 를 구 합 니 다.이것 은 바로 기하학 문제 가 아 닙 니까?중학교 수학 교과 서 를 펼 쳐 보 세 요.
보조 선 을 그 리 며 두 피 를 긁 고 긁 고α,θ,OMG...sin,cos,sh*t...드디어 r 와 D 와 n 의 관 계 를 계산 해 냈 습 니 다.

공식
사실 n=3 일 때 반경 과 n=4 일 때 는 같 지만 n=3,5 일 때 Y 축 에 편 이 량 dy 가 하나 더 있 고 r 와 dy 는 n=3,5 일 때 통식 이 있 는 것 을 고려 하여 합 쳤 다.오프셋 dy 의 공식:

공식
식 중 R 은 배치 도 에서 빨간색 큰 원 의 반지름 이다.
공식 이 있 으 면 코드 를 쓰기 쉽 습 니 다.모든 프로필 사진 의 크기 와 위 치 를 계산 하 는 코드 는 다음 과 같 습 니 다.

//      ,    、     
private static class DrawableInfo {
 int mId = View.NO_ID;
 Drawable mDrawable;
 //      
 float mCenterX;
 float mCenterY;
 //                ,               
 float mGapCenterX;
 float mGapCenterY;
 boolean mHasGap;
 //     
 final RectF mBounds = new RectF();
 //       ,       
 final Path mMaskPath = new Path();
}

private void layoutDrawables() {
 mSteinerCircleRadius = 0;
 mOffsetY = 0;

 int width = getWidth() - getPaddingLeft() - getPaddingRight();
 int height = getHeight() - getPaddingTop() - getPaddingBottom();

 mContentSize = Math.min(width, height);
 final List<DrawableInfo> drawables = mDrawables;
 final int N = drawables.size();
 float center = mContentSize * .5f;
 if (mContentSize > 0 && N > 0) {
 //       。
 final float r;
 if (N == 1) {
  r = mContentSize * .5f;
 } else if (N == 2) {
  r = (float) (mContentSize / (2 + 2 * Math.sin(Math.PI / 4)));
 } else if (N == 4) {
  r = mContentSize / 4.f;
 } else {
  r = (float) (mContentSize / (2 * (2 * Math.sin(((N - 2) * Math.PI) / (2 * N)) + 1)));
  final double sinN = Math.sin(Math.PI / N);
  //                
  final float R = (float) (r * ((sinN + 1) / sinN));
  mOffsetY = (float) ((mContentSize - R - r * (1 + 1 / Math.tan(Math.PI / N))) / 2f);
 }

 //              
 final float startX, startY;
 if (N % 2 == 0) {
  startX = startY = r;
 } else {
  startX = center;
  startY = r;
 }

 //     
 final Matrix matrix = mLayoutMatrix;
 //        
 final float[] pointsTemp = this.mPointsTemp;

 matrix.reset();

 for (int i = 0; i < drawables.size(); i++) {
  DrawableInfo drawable = drawables.get(i);
  drawable.reset();

  drawable.mHasGap = i > 0;
  //       
  if (drawable.mHasGap) {
  drawable.mGapCenterX = pointsTemp[0];
  drawable.mGapCenterY = pointsTemp[1];
  }

  pointsTemp[0] = startX;
  pointsTemp[1] = startY;
  if (i > 0) {
  //                      
  matrix.postRotate(360.f / N, center, center + mOffsetY);
  matrix.mapPoints(pointsTemp);
  }

  //        
  drawable.mCenterX = pointsTemp[0];
  drawable.mCenterY = pointsTemp[1];

  //     
  drawable.mBounds.inset(-r, -r);
  drawable.mBounds.offset(drawable.mCenterX, drawable.mCenterY);

  //   “  ”  
  drawable.mMaskPath.addCircle(drawable.mCenterX, drawable.mCenterY, r, Path.Direction.CW);
  drawable.mMaskPath.setFillType(Path.FillType.INVERSE_WINDING);
 }

 //           ,      3      
 if (N > 2) {
  DrawableInfo first = drawables.get(0);
  DrawableInfo last = drawables.get(N - 1);
  first.mHasGap = true;
  first.mGapCenterX = last.mCenterX;
  first.mGapCenterY = last.mCenterY;
 }

 mSteinerCircleRadius = r;
 }

 invalidate();
}
그리 기
모든 두상 의 크기 와 위 치 를 계산 한 후에 그것들 을 그 릴 수 있다.그러나 그 전에 두상 이미 지 를 어떻게 동 그 랗 게 만 드 느 냐 는 문제 부터 해결 해 야 한다.Drawable 대상 을 입력 하 는 데 제한 이 없 기 때 문 입 니 다.
위의 layoutDrawables 방법 에는 다음 두 줄 의 코드 가 있 습 니 다.

drawable.mMaskPath.addCircle(drawable.mCenterX, drawable.mCenterY, r, Path.Direction.CW);
drawable.mMaskPath.setFillType(Path.FillType.INVERSE_WINDING);
그 중에서 첫 번 째 줄 은 원형 경 로 를 추가 하 는 것 입 니 다.이 경 로 는 레이아웃 그림 에서 파란색 원 의 경로 이 고 두 번 째 줄 은 경 로 를 설정 하 는 충전 모드 입 니 다.기본 충전 모드 는 경로 내 부 를 채 우 는 것 이 고 INVERSE 입 니 다.WINDING 모드 는 경로 외 부 를 채 우 고 배합Paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)) 하면 원형 그림 을 그 릴 수 있 습 니 다.두상 의 결함 은 마찬가지 이다.ps:Path.FillTypePorterDuff.Mode인터넷 에 대한 소개 가 많 습 니 다.여 기 는 자세히 소개 하지 않 겠 습 니 다)
다음은 onDraw 방법 을 살 펴 보 겠 습 니 다.

@Override
protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 ...
 canvas.translate(0, mOffsetY);

 final Paint paint = mPaint;
 final float gapRadius = mSteinerCircleRadius * (mGap + 1f);
 for (int i = 0; i < drawables.size(); i++) {
  DrawableInfo drawable = drawables.get(i);
  RectF bounds = drawable.mBounds;
  final int savedLayer = canvas.saveLayer(0, 0, mContentSize, mContentSize, null, Canvas.ALL_SAVE_FLAG);

  //   Drawable   
  drawable.mDrawable.setBounds((int) bounds.left, (int) bounds.top,
    Math.round(bounds.right), Math.round(bounds.bottom));
  //   Drawable
  drawable.mDrawable.draw(canvas);

  //   “  ”  , Drawable     “ ”   
  canvas.drawPath(drawable.mMaskPath, paint);
  // “ ”      
  if (drawable.mHasGap && mGap > 0f) {
   canvas.drawCircle(drawable.mGapCenterX, drawable.mGapCenterY, gapRadius, paint);
  }

  canvas.restoreToCount(savedLayer);
 }
}
Drawable 지원
Drawable 대상 을 입력 한 이상 Bitmap 처럼 그리 면 끝 날 수 없습니다.Drawable 의 일부 기능,예 를 들 어 자체 업데이트,애니메이션,상태 등 을 지원 하지 않 는 한.
1.Drawable 자체 업데이트 와 애니메이션 Drawable
Drawable 의 자체 업데이트 와 애니메이션 Drawable(예 를 들 어 Animation Drawable,Animated Vector Drawable 등)은 모두Drawable.Callback인터페이스 에 의존 합 니 다.그 정 의 는 다음 과 같다.

public interface Callback {
 /**
  *  drawable         。   view        (  drawable      )
  * @param who        drawable
  */
 void invalidateDrawable(@NonNull Drawable who);

 /**
  * drawable                  。
  * @param who     drawable
  * @param what       
  * @param when      (      ),  android.os.SystemClock.uptimeMillis()
  */
 void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);

 /**
  * drawable                scheduleDrawable(Drawable, Runnable, long)     。
  * @param who       drawable
  * @param what         
  */
 void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
}
따라서 Drawable 자체 업데이트 와 애니메이션 Drawable 을 지원 하려 면Drawable.setCallback(Drawable.Callback)방법 으로Drawable.Callback인터페이스의 실현 대상 을 설정 해 야 합 니 다.다행히 android.view.View 이 인 터 페 이 스 를 실 현 했 습 니 다.Drawable 을 설정 할 때 호출 하면 됩 니 다Drawable.setCallback(MyView.this).그러나 주의해 야 할 것 은android.view.View Drawable.Callback인 터 페 이 스 를 실현 할 때 View.verifyDrawable(Drawable) 를 호출 하여 업 데 이 트 된 Drawable 이 자신의 Drawable 인지 검증 하고 그 실현 은 View 자신의 배경 과 전망 만 검증 했다 는 것 이다.

protected boolean verifyDrawable(@NonNull Drawable who) {
 // ...
 return who == mBackground || (mForegroundInfo != null && mForegroundInfo.mDrawable == who);
}
그래서 Callback 만 설정 하면 Drawable 내용 이 바 뀌 어서 다시 그 려 야 할 때 View 는 다시 그 려 지지 않 습 니 다.애니메이션 은 다음 프레임 을 계획 하거나 취소 해 야 할 때 성공 하지 못 합 니 다.그래서 우 리 는 자신의 Drawable 을 검증 해 야 한다.

private boolean hasSameDrawable(Drawable drawable) {
 for (DrawableInfo d : mDrawables) {
  if (d.mDrawable == drawable) {
   return true;
  }
 }
 return false;
}

@Override
protected boolean verifyDrawable(@NonNull Drawable drawable) {
 return hasSameDrawable(drawable) || super.verifyDrawable(drawable);
}
이때 Drawable 자체 업데이트 지원 과 애니메이션 Drawable 지원 은 기본적으로 완료 되 었 습 니 다.물론 View 가 보이 지 않 거나 onDetached FromWindow()를 볼 때 애니메이션 을 일시 정지 하거나 중지 해 야 합 니 다.이런 것들 은 여기 서 더 이상 말 하지 않 고 소스 코드(글 의 끝 에 링크 가 있 음)를 볼 수 있 습 니 다.주로 호출Drawable.setVisible(boolean, boolean)방법 입 니 다.
다음 효 과 를 보 여 드 리 겠 습 니 다.

AnimationDrawable
2.상태
일부 Drawable 은 상태 가 있 습 니 다.View 의 상태(누 르 기,선택,활성화 등)에 따라 표시 내용 을 바 꿀 수 있 습 니 다.예 를 들 어 StateList Drawable.View 상 태 를 지원 하려 면 확장View.drawableStateChanged() View.jumpDrawablesToCurrentState() 방법 만 있 으 면 View 상태 가 바 뀌 었 을 때 Drawable 상 태 를 업데이트 하면 됩 니 다.

//         
@Override
protected void drawableStateChanged() {
 super.drawableStateChanged();
 boolean invalidate = false;
 for (DrawableInfo drawable : mDrawables) {
  Drawable d = drawable.mDrawable;
  //   Drawable           
  if (d.isStateful() && d.setState(getDrawableState())) {
   invalidate = true;
  }
 }
 if (invalidate) {
  invalidate();
 }
}

//                    Drawable
@Override
public void jumpDrawablesToCurrentState() {
 super.jumpDrawablesToCurrentState();
 for (DrawableInfo drawable : mDrawables) {
  drawable.mDrawable.jumpToCurrentState();
 }
}
효과:

상태.
자,여기까지 컨트롤 이 완 료 된 셈 입 니 다.
기타 효과 표시:

효과

효과
프로젝트 홈 페이지:https://github.com/YiiGuxing/CompositionAvatar
로 컬 다운로드:http://xiazai.jb51.net/201704/yuanma/CompositionAvatar-master(jb51.net).rar
총결산
이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.

좋은 웹페이지 즐겨찾기