Android App 개발 에서 View 와 ViewGroup 의 인 스 턴 스 튜 토리 얼 을 사용자 정의 합 니 다.

19071 단어 AndroidViewViewGroup
View
Android 의 모든 컨트롤 은 View 또는 View 의 하위 클래스 입 니 다.사실은 화면의 사각형 영역 을 표시 합 니 다.Rect 로 표시 합 니 다.left,top 은 View 가 parent View 의 출발점 에 비해 width,height 는 View 자신의 너비 와 높이 를 표시 합 니 다.이 4 개의 필드 를 통 해 View 가 화면 에 있 는 위 치 를 확인 할 수 있 습 니 다.위 치 를 확인 하면 View 의 내용 을 그 릴 수 있 습 니 다.
그리 기 프로 세 스 보기
View 의 그리 기 는 다음 세 가지 과정 으로 나 눌 수 있 습 니 다.
Measure
View 는 먼저 측정 을 해서 자신 이 얼마나 큰 면적 을 차지 해 야 하 는 지 계산한다.View 의 Measure 과정 은 우리 에 게 인터페이스 onMeasure 를 노출 시 켰 다.방법의 정 의 는 이렇다.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}
View 류 는 기본 적 인 onMeasure 실현 을 제 공 했 습 니 다.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
     getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
 int result = size;
 int specMode = MeasureSpec.getMode(measureSpec);
 int specSize = MeasureSpec.getSize(measureSpec);

 switch (specMode) {
 case MeasureSpec.UNSPECIFIED:
   result = size;
   break;
 case MeasureSpec.AT_MOST:
 case MeasureSpec.EXACTLY:
   result = specSize;
   break;
 }
 return result;
}

그 중에서 invoke 는 set Measured Dimension()방법 을 설 정 했 고 measure 과정 에서 View 의 너비 와 높이 를 설 정 했 으 며 getSuggested MinimumWidth()는 View 의 최소 Width 를 되 돌려 주 었 고 Height 도 대응 하 는 방법 이 있 습 니 다.몇 마디 삽입 하면 MeasureSpec 류 는 View 류 의 내부 정적 류 로 세 개의 상수 UNSPECIFIED,AT 를 정의 합 니 다.MOST,EXACTLY,사실 우 리 는 이렇게 이해 할 수 있 습 니 다.LayoutParams 에서 match 에 대응 합 니 다.parent、wrap_content、xxxdp。우 리 는 onMeasure 를 다시 써 서 View 의 너비 와 높이 를 다시 정의 할 수 있다.
Layout
Layout 과정 은 View 류 에 대해 매우 간단 합 니 다.마찬가지 로 View 는 우리 에 게 onLayout 방법 을 노출 시 켰 습 니 다.

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
우리 가 지금 토론 하고 있 는 것 은 View 이기 때문에 서브 View 가 배열 해 야 할 것 이 없 기 때문에 이 단 계 는 사실 우 리 는 추가 적 인 일 을 할 필요 가 없다.ViewGroup 류,onLayout 방법 에 대해 서 는 모든 하위 View 의 크기 와 높이 를 설정 해 야 합 니 다.이것 은 다음 편 에서 자세히 말씀 드 리 겠 습 니 다.
Draw
Draw 과정 은 canvas 에 우리 가 필요 로 하 는 View 스타일 을 그 리 는 것 입 니 다.마찬가지 로 View 가 저희 에 게 onDraw 방법 을 노출 시 켰 습 니 다.

protected void onDraw(Canvas canvas) {
}
기본 View 류 의 onDraw 는 코드 가 한 줄 도 없 지만 우리 에 게 빈 캔버스 를 제공 합 니 다.예 를 들 어 그림 한 장 처럼 우 리 는 화가 입 니 다.어떤 효 과 를 그 릴 수 있 는 지 는 우리 에 게 달 려 있 습 니 다.
View 에는 세 가지 중요 한 방법 이 있 습 니 다.
requestLayout
View 에서 layot 프로 세 스 를 다시 호출 합 니 다.
invalidate
View draw 프로 세 스 다시 호출
forceLayout
표지 View 는 다음 에 다시 그립 니 다.layot 과정 을 다시 호출 해 야 합 니 다.
사용자 정의 속성
전체 View 의 그리 기 프로 세 스 를 소 개 했 습 니 다.그리고 중요 한 지식 이 있 습 니 다.컨트롤 속성 을 사용자 정의 합 니 다.View 는 기본 적 인 속성 이 있다 는 것 을 알 고 있 습 니 다.예 를 들 어 layotwidth,layout_height,background 등 우 리 는 자신의 속성 을 정의 해 야 한다.그러면 구체 적 으로 이렇게 할 수 있다.
1.values 폴 더 에서 attrs.xml 를 엽 니 다.사실 이 파일 의 이름 은 임의의 것 일 수 있 습 니 다.여기에 쓰 면 더욱 규범 적 이 고 안에 있 는 것 이 모두 view 의 속성 임 을 표시 합 니 다.
2.아래 의 인 스 턴 스 는 2 개의 길이,하나의 색상 값 의 속성 을 사용 하기 때문에 3 개의 속성 을 만 듭 니 다.

<declare-styleable name="rainbowbar">
 <attr name="rainbowbar_hspace" format="dimension"></attr>
 <attr name="rainbowbar_vspace" format="dimension"></attr>
 <attr name="rainbowbar_color" format="color"></attr>
</declare-styleable>
그렇다면 도대체 어떻게 사용 할 것 인가?우 리 는 실례 를 볼 것 이다.
비교적 간단 한 Google 무지개 진행 표시 줄 을 구현 합 니 다.
간단하게 보기 위해 서 여 기 는 한 가지 색깔 만 사용 하고 여러 가지 색깔 은 여러분 께 남 겨 드 리 겠 습 니 다.우 리 는 바로 코드 를 올 립 니 다.
201656180823453.png (814×106)

public class RainbowBar extends View {

 //progress bar color
 int barColor = Color.parseColor("#1E88E5");
 //every bar segment width
 int hSpace = Utils.dpToPx(80, getResources());
 //every bar segment height
 int vSpace = Utils.dpToPx(4, getResources());
 //space among bars
 int space = Utils.dpToPx(10, getResources());
 float startX = 0;
 float delta = 10f;
 Paint mPaint;

 public RainbowBar(Context context) {
  super(context);
 }

 public RainbowBar(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }

 public RainbowBar(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  //read custom attrs
  TypedArray t = context.obtainStyledAttributes(attrs,
      R.styleable.rainbowbar, 0, 0);
  hSpace = t.getDimensionPixelSize(R.styleable.rainbowbar_rainbowbar_hspace, hSpace);
  vSpace = t.getDimensionPixelOffset(R.styleable.rainbowbar_rainbowbar_vspace, vSpace);
  barColor = t.getColor(R.styleable.rainbowbar_rainbowbar_color, barColor);
  t.recycle();  // we should always recycle after used
  mPaint = new Paint();
  mPaint.setAntiAlias(true);
  mPaint.setColor(barColor);
  mPaint.setStrokeWidth(vSpace);
 }

 .......
}

View 는 세 가지 구조 방법 이 있 는데 우리 가 다시 써 야 한다.여기 서 다음 세 가지 방법 이 호출 될 장면 을 소개 한다.
첫 번 째 방법 은 일반적으로 우리 가 이렇게 사용 할 때 호출 됩 니 다.View view=new View(context);
두 번 째 방법 은 xml 레이아웃 파일 에서 View 를 사용 할 때 inflate 레이아웃 에서 호출 됩 니 다.

세 번 째 방법 은 두 번 째 방법 과 유사 하지만 style 속성 설정 을 증가 합 니 다.이때 inflater 레이아웃 은 세 번 째 구조 방법 을 사용 합 니 다.

위 에서 약간 곤 혹 스 러 워 하 실 수 있 는 것 은 제 가 사용자 정의 속성 hspace,vspace,barcolor 코드 를 세 번 째 구조 방법 에 초기 화 했 지만 Rainbow Bar 는 선형 구조 에 style 속성()을 추가 하지 않 았 습 니 다.그러면 우리 위의 해석 에 따 르 면 inflate 구 조 를 할 때 invoke 두 번 째 구조 방법 이 있 을 것 입 니 다.그러나 우 리 는 두 번 째 구조 방법 에서 세 번 째 구조 방법 인 this(context,attrs,0)를 호출 했다.그래서 세 번 째 구조 방법 에서 사용자 정의 속성 을 읽 는 데 문제 가 없습니다.이것 은 작은 디 테 일 입 니 다.코드 가 불필요 하지 않도록-...
Draw
measrue 와 layot 과정 에 관심 을 가지 지 않 고 onDraw 방법 을 직접 다시 쓰 면 되 기 때문이다.
 

//draw be invoke numbers.
int index = 0;
@Override
protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  //get screen width
  float sw = this.getMeasuredWidth();
  if (startX >= sw + (hSpace + space) - (sw % (hSpace + space))) {
    startX = 0;
  } else {
    startX += delta;
  }
  float start = startX;
  // draw latter parse
  while (start < sw) {
    canvas.drawLine(start, 5, start + hSpace, 5, mPaint);
    start += (hSpace + space);
  }

  start = startX - space - hSpace;

  // draw front parse
  while (start >= -hSpace) {
    canvas.drawLine(start, 5, start + hSpace, 5, mPaint);
    start -= (hSpace + space);
  }
  if (index >= 700000) {
    index = 0;
  }
  invalidate();
}

레이아웃 파일:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout   xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_marginTop="40dp"
android:orientation="vertical" >

<com.sw.demo.widget.RainbowBar 
    android:layout_width="match_parent"
  android:layout_height="wrap_content"
  app:rainbowbar_color="@android:color/holo_blue_bright"
  app:rainbowbar_hspace="80dp"
  app:rainbowbar_vspace="10dp"
  ></com.sw.demo.widget.RainbowBar>

</LinearLayout>

사실은 canvas 의 drawLine 방법 을 호출 한 다음 에 draw 의 출발점 을 앞으로 추진 하 는 것 입 니 다.방법의 끝 에 우 리 는 invalidate 방법 을 호출 했 습 니 다.위 에서 설명 한 바 와 같이 이 방법 은 View 로 하여 금 onDraw 방법 을 다시 호출 하 게 하기 때문에 우리 의 진도 항목 이 계속 앞으로 그 려 지 는 효 과 를 얻 을 수 있 습 니 다.다음은 마지막 디 스 플레이 효과 입 니 다.gif 로 만 들 때 색 차 가 있 는 것 같 지만 실제 효 과 는 파란색 입 니 다.우 리 는 짧 은 몇 십 줄 의 코드 만 썼 습 니 다.사용자 정의 View 는 우리 가 상상 하 는 것 만큼 어렵 지 않 습 니 다.다음 편 은 View Group 의 그리 기 프로 세 스 학습 을 계속 할 것 입 니 다.
201656181022998.gif (404×720)
사용자 정의 ViewGroup
ViewGroup
우 리 는 View Group 이 View 의 용기 류 라 는 것 을 알 고 있 습 니 다.우리 가 자주 사용 하 는 LinearLayout,RelativeLayout 등 은 모두 View Group 의 하위 클래스 입 니 다.View Group 은 하위 View 가 많 기 때문에 전체 그리 기 과정 은 View 에 비해 복잡 하지만 세 가지 절차 measure,layot,draw 입 니 다.우 리 는 한 번 설명 합 니 다.
Measure
Measure 프로 세 스 는 ViewGroup 의 크기 를 측정 합 니 다.만약 layotwidht 와 layotheight 는 matchparent 또는 구체 적 인 xxxdp 는 간단 합 니 다.set Measured Dimension()방법 을 직접 호출 하여 View Group 의 너비 와 높이 를 설정 하면 됩 니 다.wrap 이 라면.content 는 비교적 번 거 롭 습 니 다.우 리 는 모든 하위 View 를 옮 겨 다 닌 다음 에 모든 하위 View 를 측정 한 다음 에 하위 View 의 배열 규칙 에 따라 최종 View Group 의 크기 를 계산 해 야 합 니 다.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 int childCount = this.getChildCount();
 for (int i = 0; i < childCount; i++) {
   View child = this.getChildAt(i);
   this.measureChild(child, widthMeasureSpec, heightMeasureSpec);
   int cw = child.getMeasuredWidth();
   // int ch = child.getMeasuredHeight();
 }
}
getChildCount()방법 을 사용 하여 하위 View 의 수량,measure Child()방법 을 되 돌려 주 고 하위 View 의 측정 방법 을 사용 해 야 할 수도 있 습 니 다.
Layout
위 View 의 사용자 정의 에서 우 리 는 layot 과정 은 사실 하위 View 의 위 치 를 배열 하 는 것 이 라 고 약간 언급 했다.onLayout 방법 은 나 에 게 우리 가 원 하 는 규칙 에 따라 의 자 View 를 배열 할 기 회 를 주 었 다.

@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
 int childCount = this.getChildCount();
 for (int i = 0; i < childCount; i++) {
   View child = this.getChildAt(i);
   LayoutParams lParams = (LayoutParams) child.getLayoutParams();
   child.layout(lParams.left, lParams.top, lParams.left + childWidth,
       lParams.top + childHeight);
 }
}
위 와 같은 코드 가 필요 할 수도 있 습 니 다.그 중에서 child.layot(left,top,right,bottom)방법 은 하위 View 의 위 치 를 설정 할 수 있 습 니 다.네 개의 매개 변 수 는 변 수 를 통 해 모두 가 알 아야 합 니 다.
Draw
ViewGroup 은 draw 단계 에 있 습 니 다.사실은 하위 클래스 의 배열 순서에 따라 하위 클래스 의 onDraw 방법 을 호출 하 는 것 입 니 다.우 리 는 View 의 용기 일 뿐 그 자체 가 draw 추가 적 인 수식 이 필요 하지 않 기 때문에 onDraw 방법 에 서 는 ViewGroup 의 onDraw 기본 구현 방법 만 사용 하면 됩 니 다.
LayoutParams
ViewGroup 에 중요 한 지식 이 하나 더 있 습 니 다.LayoutParams 는 하위 View 가 ViewGroup 에 가입 할 때의 매개 변수 정 보 를 저장 합 니 다.ViewGroup 클래스 를 계승 할 때 보통 새로운 LayoutParams 클래스 를 새로 만들어 야 합 니 다.SDK 에서 우리 가 잘 아 는 LinearLayout.LayoutParams,RelativeLayout.LayoutParams 클래스 등 과 같이 이렇게 할 수 있 습 니 다.정 의 된 ViewGroup 하위 클래스 에 LayoutParams 클래스 계승 과 ViewGroup.LayoutParams 를 새로 만 듭 니 다.

public static class LayoutParams extends ViewGroup.LayoutParams {

 public int left = 0;
 public int top = 0;

 public LayoutParams(Context arg0, AttributeSet arg1) {
   super(arg0, arg1);
 }

 public LayoutParams(int arg0, int arg1) {
   super(arg0, arg1);
 }

 public LayoutParams(android.view.ViewGroup.LayoutParams arg0) {
   super(arg0);
 }

}

이제 새로운 LayoutParams 클래스 가 생 겼 습 니 다.사용자 정의 ViewGroup 은 사용자 정의 LayoutParams 클래스 를 사용 하여 하위 View 를 추가 할 수 있 습 니까?ViewGroup 역시 다음 과 같은 몇 가지 방법 을 제공 합 니 다.사용자 정의 LayoutParams 대상 을 다시 쓰 면 됩 니 다.

@Override
public android.view.ViewGroup.LayoutParams generateLayoutParams(
   AttributeSet attrs) {
 return new NinePhotoView.LayoutParams(getContext(), attrs);
}

@Override
protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {
 return new LayoutParams(LayoutParams.WRAP_CONTENT,
     LayoutParams.WRAP_CONTENT);
}

@Override
protected android.view.ViewGroup.LayoutParams generateLayoutParams(
   android.view.ViewGroup.LayoutParams p) {
 return new LayoutParams(p);
}

@Override
protected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams p) {
 return p instanceof NinePhotoView.LayoutParams;
}

실례
우 리 는 오늘 위 챗 모멘트 에 그림 을 보 낼 컨트롤 을 만 들 고+번 그림 을 누 르 면 그림 을 계속 추가 할 수 있 습 니 다.최대 9 장 까지 추가 할 수 있 습 니 다.그러면 위 챗 은 4 개 1 열 입 니 다.여 기 는 3 개 1 열 입 니 다.보통 3 개 1 열 이기 때문에 디 테 일 에 신경 쓰 지 마 세 요.
201656181132418.png (632×294)

public class NinePhotoView extends ViewGroup {

public static final int MAX_PHOTO_NUMBER = 9;

private int[] constImageIds = { R.drawable.girl_0, R.drawable.girl_1,
   R.drawable.girl_2, R.drawable.girl_3, R.drawable.girl_4,
   R.drawable.girl_5, R.drawable.girl_6, R.drawable.girl_7,
   R.drawable.girl_8 };

// horizontal space among children views
int hSpace = Utils.dpToPx(10, getResources());
// vertical space among children views
int vSpace = Utils.dpToPx(10, getResources());

// every child view width and height.
int childWidth = 0;
int childHeight = 0;

// store images res id
ArrayList<integer> mImageResArrayList = new ArrayList<integer>(9);
private View addPhotoView;

public NinePhotoView(Context context) {
 super(context);
}

public NinePhotoView(Context context, AttributeSet attrs) {
 this(context, attrs, 0);
}

public NinePhotoView(Context context, AttributeSet attrs, int defStyle) {
 super(context, attrs, defStyle);

 TypedArray t = context.obtainStyledAttributes(attrs,
     R.styleable.NinePhotoView, 0, 0);
 hSpace = t.getDimensionPixelSize(
     R.styleable.NinePhotoView_ninephoto_hspace, hSpace);
 vSpace = t.getDimensionPixelSize(
     R.styleable.NinePhotoView_ninephoto_vspace, vSpace);
 t.recycle();

 addPhotoView = new View(context);
 addView(addPhotoView);
 mImageResArrayList.add(new integer());
}

지금까지 전편 에서 말 한 것 과 크게 다 르 지 않 았 습 니 다.또한 사진 을 찍 는 것 과 앨범 에서 사진 을 선택 하 는 것 이 우리 의 중점 이 아니 었 습 니 다.그래서 우 리 는 사진 을 코드 에 하 드 인 코딩 했 습 니 다.(모두 미녀...)ViewGroup 초기 화 할 때 우 리 는+번호 단 추 를 추가 하여 사용자 에 게 새로운 그림 을 추가 하려 고 클릭 했 습 니 다.
Measure

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 int rw = MeasureSpec.getSize(widthMeasureSpec);
 int rh = MeasureSpec.getSize(heightMeasureSpec);

 childWidth = (rw - 2 * hSpace) / 3;
 childHeight = childWidth;

 int childCount = this.getChildCount();
 for (int i = 0; i < childCount; i++) {
   View child = this.getChildAt(i);
   //this.measureChild(child, widthMeasureSpec, heightMeasureSpec);

   LayoutParams lParams = (LayoutParams) child.getLayoutParams();
   lParams.left = (i % 3) * (childWidth + hSpace);
   lParams.top = (i / 3) * (childWidth + vSpace);
 }

 int vw = rw;
 int vh = rh;
 if (childCount < 3) {
   vw = childCount * (childWidth + hSpace);
 }
 vh = ((childCount + 3) / 3) * (childWidth + vSpace);
 setMeasuredDimension(vw, vh);
}

우리 의 하위 View 는 세 줄 로 되 어 있 고 모두 정사각형 이기 때문에 우 리 는 위 에서 순환 을 통 해 모든 하위 View 의 위 치 를 얻 을 수 있 습 니 다.위 에서 하위 View 의 왼쪽 상단 좌 표를 우리 가 정의 한 Layout Params 의 left 와 top 두 필드 에 저장 합 니 다.Layout 단 계 는 사용 할 것 입 니 다.마지막 으로 우 리 는 전체 View Group 의 너비 와 높이 를 계산 하고 set Measured Dimension 설정 을 호출 합 니 다.
Layout

@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
 int childCount = this.getChildCount();
 for (int i = 0; i < childCount; i++) {
   View child = this.getChildAt(i);
   LayoutParams lParams = (LayoutParams) child.getLayoutParams();
   child.layout(lParams.left, lParams.top, lParams.left + childWidth,
       lParams.top + childHeight);

   if (i == mImageResArrayList.size() - 1 && mImageResArrayList.size() != MAX_PHOTO_NUMBER) {
     child.setBackgroundResource(R.drawable.add_photo);
     child.setOnClickListener(new View.OnClickListener() {

       @Override
       public void onClick(View arg0) {
         addPhotoBtnClick();
       }
     });
   }else {
     child.setBackgroundResource(constImageIds[i]);
     child.setOnClickListener(null);
   }
 }
}

public void addPhoto() {
 if (mImageResArrayList.size() < MAX_PHOTO_NUMBER) {
   View newChild = new View(getContext());
   addView(newChild);
   mImageResArrayList.add(new integer());
   requestLayout();
   invalidate();
 }
}

public void addPhotoBtnClick() {
 final CharSequence[] items = { "Take Photo", "Photo from gallery" };

 AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
 builder.setItems(items, new DialogInterface.OnClickListener() {

   @Override
   public void onClick(DialogInterface arg0, int arg1) {
     addPhoto();
   }

 });
 builder.show();
}

가장 핵심 적 인 것 은 layot 방법 을 호출 하 는 것 입 니 다.우리 measure 단계 에서 얻 은 LayoutParams 의 left 와 top 필드 에 따라 모든 하위 View 에 위 치 를 배열 하 는 것 도 좋 습 니 다.그 다음 에 그림 이 최대 치 9 장 에 이 르 지 못 했다 고 판단 할 때 기본 값 은+번호 그림 이 고 클릭 이벤트,팝 업 대화 상 자 를 설정 하여 사용자 가 선택 할 수 있 도록 합 니 다.
Draw
다시 쓸 필요 가 없습니다.ViewGroup 기본 값 으로 실행 하면 됩 니 다.
레이아웃 파일 첨부

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="40dp"
android:orientation="vertical" >

<com.sw.demo.widget.NinePhotoView
  android:id="@+id/photoview"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  app:ninephoto_hspace="10dp"
  app:ninephoto_vspace="10dp"
  app:rainbowbar_color="@android:color/holo_blue_bright" >

</com.sw.demo.widget.NinePhotoView>

</LinearLayout>

마지막 으로 프로그램 이 실 행 된 효과 도 를 더 해서 오늘 사용자 정의 View Group 의 설명 이 이렇게 많 습 니 다.매일 새로운 수확 을 얻 고 매일 기분 이 좋 으 시 길 바 랍 니 다~~

좋은 웹페이지 즐겨찾기