Android App 개발 에서 View 와 ViewGroup 의 인 스 턴 스 튜 토리 얼 을 사용자 정의 합 니 다.
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 무지개 진행 표시 줄 을 구현 합 니 다.
간단하게 보기 위해 서 여 기 는 한 가지 색깔 만 사용 하고 여러 가지 색깔 은 여러분 께 남 겨 드 리 겠 습 니 다.우 리 는 바로 코드 를 올 립 니 다.
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 의 그리 기 프로 세 스 학습 을 계속 할 것 입 니 다.사용자 정의 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 열 이기 때문에 디 테 일 에 신경 쓰 지 마 세 요.
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 의 설명 이 이렇게 많 습 니 다.매일 새로운 수확 을 얻 고 매일 기분 이 좋 으 시 길 바 랍 니 다~~이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 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에 따라 라이센스가 부여됩니다.