안드로이드 개발 학습의 019 사용자 정의 보기 만들기
새 클래스는 View 클래스에서 상속됩니다.
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
캔버스에 도안을 그리려면 펜 (Paint 대상) 이 필요합니다. 여기에 미리 정의한 다음 두 번째 구조 함수에서 그림을 만들고 간단한 설정을 합니다.
private static final String TAG = MyView.class.getSimpleName();
private Paint mPaint;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
//
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setTextSize(30);
//
mPaint.setStrokeWidth(3);
}
컨트롤 크기 조정/다시 쓰기 onMeasure 방법
컨트롤의 부모 용기가 하위 컨트롤을 레이아웃할 때 onMeasure 방법을 호출합니다.그것은 "당신은 얼마나 큰 공간을 사용해야 합니까?"라고 제기했다.이러한 질문은 두 개의 매개 변수를 동시에 전달합니다:widthMeasureSpec와heightMeasureSpec.아래와 같다
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
이 방법을 다시 쓰기 전에 Google의 TextView가 어떻게 이루어졌는지 살펴봅시다.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
BoringLayout.Metrics boring = UNKNOWN_BORING;
BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
if (mTextDir == null) {
mTextDir = getTextDirectionHeuristic();
}
int des = -1;
boolean fromexisting = false;
if (widthMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
width = widthSize;
} else {
if (mLayout != null && mEllipsize == null) {
des = desired(mLayout);
}
if (des < 0) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
if (boring != null) {
mBoring = boring;
}
} else {
fromexisting = true;
}
if (boring == null || boring == UNKNOWN_BORING) {
if (des < 0) {
des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
}
width = des;
} else {
width = boring.width;
}
final Drawables dr = mDrawables;
if (dr != null) {
width = Math.max(width, dr.mDrawableWidthTop);
width = Math.max(width, dr.mDrawableWidthBottom);
}
if (mHint != null) {
int hintDes = -1;
int hintWidth;
if (mHintLayout != null && mEllipsize == null) {
hintDes = desired(mHintLayout);
}
if (hintDes < 0) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
}
if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
if (hintDes < 0) {
hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
}
hintWidth = hintDes;
} else {
hintWidth = hintBoring.width;
}
if (hintWidth > width) {
width = hintWidth;
}
}
width += getCompoundPaddingLeft() + getCompoundPaddingRight();
if (mMaxWidthMode == EMS) {
width = Math.min(width, mMaxWidth * getLineHeight());
} else {
width = Math.min(width, mMaxWidth);
}
if (mMinWidthMode == EMS) {
width = Math.max(width, mMinWidth * getLineHeight());
} else {
width = Math.max(width, mMinWidth);
}
// Check against our minimum width
width = Math.max(width, getSuggestedMinimumWidth());
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
}
int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
int unpaddedWidth = want;
if (mHorizontallyScrolling) want = VERY_WIDE;
int hintWant = want;
int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
if (mLayout == null) {
makeNewLayout(want, hintWant, boring, hintBoring,
width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
} else {
final boolean layoutChanged = (mLayout.getWidth() != want) ||
(hintWidth != hintWant) ||
(mLayout.getEllipsizedWidth() !=
width - getCompoundPaddingLeft() - getCompoundPaddingRight());
final boolean widthChanged = (mHint == null) &&
(mEllipsize == null) &&
(want > mLayout.getWidth()) &&
(mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
if (layoutChanged || maximumChanged) {
if (!maximumChanged && widthChanged) {
mLayout.increaseWidthTo(want);
} else {
makeNewLayout(want, hintWant, boring, hintBoring,
width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
}
} else {
// Nothing has changed
}
}
if (heightMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
height = heightSize;
mDesiredHeightAtMeasure = -1;
} else {
int desired = getDesiredHeight();
height = desired;
mDesiredHeightAtMeasure = desired;
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desired, heightSize);
}
}
int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
}
/*
* We didn't let makeNewLayout() register to bring the cursor into view,
* so do it here if there is any possibility that it is needed.
*/
if (mMovement != null ||
mLayout.getWidth() > unpaddedWidth ||
mLayout.getHeight() > unpaddedHeight) {
registerForPreDraw();
} else {
scrollTo(0, 0);
}
setMeasuredDimension(width, height);
}
기절, 간단한 TextView의 onMeasure 코드가 매우 길다. 우리는 이 방법을 세심하게 분석한 결과 크게 세 부분으로 나뉘었다. 먼저 MeasureSpec이라는 종류를 통해 길이와 크기를 얻은 다음에 일련의 판단에 따라 실제 길이와 넓이를 얻었다. 마지막으로 setMeasured Dimension(width, height) 방법으로 컨트롤의 실제 넓이와 높이를 설정했다.이 메이저스펙은 또 뭔가요?저희가 이런 걸 알아야 될 것 같아요.
MeasureSpec 클래스 소개
이것은 View의 내부 클래스입니다. 정의는 다음과 같습니다.
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* Creates a measure specification based on the supplied size and mode.
*
* The mode must always be one of the following:
*
* - {@link android.view.View.MeasureSpec#UNSPECIFIED}
* - {@link android.view.View.MeasureSpec#EXACTLY}
* - {@link android.view.View.MeasureSpec#AT_MOST}
*
*
* Note: On API level 17 and lower, makeMeasureSpec's
* implementation was such that the order of arguments did not matter
* and overflow in either value could impact the resulting MeasureSpec.
* {@link android.widget.RelativeLayout} was affected by this bug.
* Apps targeting API levels greater than 17 will get the fixed, more strict
* behavior.
*
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
/**
* Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
* will automatically get a size of 0. Older apps expect this.
*
* @hide internal use only for compatibility with system widgets and older apps
*/
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
int size = getSize(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return makeMeasureSpec(size, UNSPECIFIED);
}
size += delta;
if (size < 0) {
Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
") spec: " + toString(measureSpec) + " delta: " + delta);
size = 0;
}
return makeMeasureSpec(size, mode);
}
/**
* Returns a String representation of the specified measure
* specification.
*
* @param measureSpec the measure specification to convert to a String
* @return a String with the following format: "MeasureSpec: MODE SIZE"
*/
public static String toString(int measureSpec) {
int mode = getMode(measureSpec);
int size = getSize(measureSpec);
StringBuilder sb = new StringBuilder("MeasureSpec: ");
if (mode == UNSPECIFIED)
sb.append("UNSPECIFIED ");
else if (mode == EXACTLY)
sb.append("EXACTLY ");
else if (mode == AT_MOST)
sb.append("AT_MOST ");
else
sb.append(mode).append(" ");
sb.append(size);
return sb.toString();
}
}
이 도구류는 주로 네 가지 방법과 세 가지 상수가 있다. UNSPECIFIED: 0 왼쪽으로 30자리, 최고 두 자리는 00 EXACTLY: 1 왼쪽으로 30자리, 최고 두 자리는 01 ATMOST:2 왼쪽으로 30, 가장 높은 두 자리는 10
//이것은 우리가 제시한 사이즈 크기와 패턴으로 이 두 가지 정보를 포함하는 int 변수를 생성합니다. 여기 이 패턴의 매개 변수는 세 개의 상수 중 하나를 전달합니다.public static int makeMeasureSpec(int size, int mode)
//이것은 이 변수에 표시된 패턴 정보를 얻어 세 개의 상수 중 하나를 되돌려준다.public static int getMode(int measureSpec)
//이것은 이 변수에 표시된 사이즈 크기를 얻는 값이다.public static int getSize(int measureSpec)
//로그public static String toString (int measure Spec) 을 쉽게 칠 수 있도록 이 변수의 패턴과 크기를 문자열로 되돌려줍니다.
TextView의 onMeasure 방법에서 볼 수 있습니다.
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int형 변수를 어떻게 모드와 사이즈 값을 얻을 수 있습니까?비결은 int형 정수가 32자리인데 가장 높은 2자리 표시 모드를 나타낸다. 그 다음에 30자리는 크기가 어떻게 한 정수의 가장 높은 두 자리를 얻을 수 있는지를 나타낸다. 11000000000000000000000000000000000000000000000000000000000000000과 위치를 바꾸어 얻을 수 있다.
최고 두 자리는 00일 때'미지정 모드'를 나타낸다.즉, MeasureSpec.UNSPECIFIED의 최고 두 자리는 01에'정밀모드'를 나타낸다.즉, MeasureSpec.EXACTLY 최대 두 자리는 11에'최대 모드'를 나타낸다.즉, MeasureSpec.AT_MOST
그러면 어떻게 30자리의 수치를 얻습니까?01111111111111111111111111111111와 함께
그렇다면 세 가지 모델은 구체적으로 무엇을 대표하는가?UNSPECIFIED: 모 레이아웃은 자 레이아웃에 아무런 제한이 없으며 자 레이아웃은 임의의 크기로 할 수 있습니다.예를 들어 컨트롤 Scroll View/Horizontal Scroll View는 EXACTLY입니다. 부모 레이아웃은 하위 요소의 정확한 크기를 결정하고 하위 요소는 주어진 경계에 한정되며 그 자체의 크기를 무시합니다.보통 레이아웃width가 명확한 값을 설정하거나 MATCHPARENT AT_MOST: 하위 레이아웃은 크기에 따라 원하는 크기를 선택할 수 있습니다.일반 layoutwidth는 WARP 입니다.CONTENT
MeasureSpec 클래스에 대해 기본적으로 알고 난 후, 우리는 온 Measure 방법을 다시 쓰기 시작했다
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//
int measuredWidth = measureWidth(widthMeasureSpec);
int measuredHeight = measureHeight(heightMeasureSpec);
// setMeasuredDimension,
setMeasuredDimension(measuredWidth, measuredHeight);
}
private int measureHeight(int heightMeasureSpec) {
//
int result = 200;
//
int padding = getPaddingTop() + getPaddingBottom();
//
int specMode = MeasureSpec.getMode(heightMeasureSpec);
//
int specSize = MeasureSpec.getSize(heightMeasureSpec);
//
Log.i(TAG," " + MeasureSpec.toString(heightMeasureSpec));
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
break;
case MeasureSpec.AT_MOST:
//result = specSize;
// ondraw 。
result = Math.min(heightMeasureSpec,measureTextHeight()+padding);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
private int measureWidth(int widthMeasureSpec) {
//
int result = 2000;
//
int padding = getPaddingLeft() + getPaddingRight();
//
int specMode = MeasureSpec.getMode(widthMeasureSpec);
//
int specSize = MeasureSpec.getSize(widthMeasureSpec);
//
Log.i(TAG, " " + MeasureSpec.toString(widthMeasureSpec));
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
break;
case MeasureSpec.AT_MOST:
result = Math.min(widthMeasureSpec,specSize+padding);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
//
private int measureTextHeight() {
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
// System.out.println("fontMetrics = " + fontMetrics);
return fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading;
}
onDraw 다시 쓰기 방법
본문은 단지 화포에 문자열을 한 줄 썼을 뿐, 비교적 간단하다.
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("MyView", 0, getHeight(), mPaint);
}
사용자 정의 보기를 사용하고 세 가지 모드를 검증합니다
XML에 사용자 정의 뷰 4개 추가하기
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.antex.myview.MainActivity"
tools:showIn="@layout/activity_main">
<HorizontalScrollView android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.antex.myview.MyView
android:layout_width="wrap_content"
android:layout_height="60dp"
android:text="Hello World!"/>
HorizontalScrollView>
<com.antex.myview.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="20dp"
android:text="Hello World!"/>
<com.antex.myview.MyView
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="Hello World!"/>
<com.antex.myview.MyView
android:layout_width="100dp"
android:layout_height="60dp"
android:text="Hello World!"/>
LinearLayout>
첫 번째 보기는 Horizontal Scroll View에 감싸여 있고 부모 보기의 너비는 정해지지 않습니다. 그러면 첫 번째 보기의 너비 모드는 UNSPECIFIED 두 번째 보기의layoutwidth=”wrap_콘텐츠MOST 세 번째 및 네 번째 뷰의 layoutwidth를 match 로 지정parent의 정확한 값이 무엇인지, 그 너비 모드는 EXACTLY이어야 한다.
다음 프로그램을 실행하여 이 결론을 검증합니다. 출력 로그는 12-19 23:13:54.822 2101-2101/?I/MyView: 측정 너비 MeasureSpec: UNSPECIFIED 0 12-19 23:13:54.822 2101-2101/?I/MyView: 높이 측정 MeasureSpec: EXACTLY 158 12-19 23:13:54.822 2101-2101/?I/MyView: 너비 측정 MeasureSpec: ATMOST 996 12-19 23:13:54.822 2101-2101/? I/MyView: 높이 측정 MeasureSpec: ATMOST 1489 12-19 23:13:54.822 2101-2101/? I/MyView: 측정 너비 MeasureSpec: EXACTLY 996 12-19 23:13:54.822 2101-2101/?I/MyView: 높이 측정 MeasureSpec: EXACTLY 158 12-19 23:13:54.822 2101-2101/?I/MyView: 측정 너비 MeasureSpec: EXACTLY 263 12-19 23:13:54.822 2101-2101/?I/MyView: 고도 측정 MeasureSpec: EXACTLY 158 로그를 통해 우리가 생각한 것과 일치하는 것을 발견할 수 있습니다~~~
자, 본문은 여기까지 소개하고 자신만의 보기를 만들기 시작합시다.다음은 사용자 정의 View의 이벤트 상호 작용과 Canvas 상세 설명
개발 도구: Android Studio1.5 SDK: Android 6.0 API 23
코드 다운로드: 019MyView_onMeasure
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Kotlin의 기초 - 2부지난 글에서는 Kotlin이 무엇인지, Kotlin의 특징, Kotlin에서 변수 및 데이터 유형을 선언하는 방법과 같은 Kotlin의 기본 개념에 대해 배웠습니다. 유형 변환은 데이터 변수의 한 유형을 다른 데이터...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.