클리핑 객체

이 Codelab은 Kotlin 기반 Android 고급 교육 과정의 일부입니다. Codelab을 순서대로 진행하는 경우 학습 효과를 극대화할 수 있지만 순서를 바꿔 진행해도 괜찮습니다. 모든 과정 Codelab은 Kotlin Codelab의 고급 Android Codelab 방문 페이지에 나열되어 있습니다.

소개

이 Codelab에서 자르기는 선택적으로 그려지거나 화면에 그려지지 않는 이미지, 캔버스, 비트맵의 영역을 정의하는 방법입니다. 클리핑의 한 가지 목적은 오버드로를 줄이는 것입니다. 오버드로는 최종 이미지를 표시하기 위해 화면의 픽셀을 두 번 이상 그리는 것입니다. 오버드로를 줄일 때는 그리기 성능을 최대화하기 위해 디스플레이의 픽셀이나 영역을 그리는 횟수를 최소화합니다. 클리핑을 사용하여 사용자 인터페이스 디자인 및 애니메이션에 흥미로운 효과를 만들 수도 있습니다.

예를 들어, 아래에 나와 있는 것처럼 겹치는 카드 스택을 그리는 경우 일반적으로 각 카드를 아래에서 위로 그리는 대신 보이는 부분만 그리는 것이 더 효율적입니다. '보통'은 클리핑 연산에도 비용이 들기 때문에 전반적으로 Android 시스템은 많은 그리기 최적화를 실행합니다.

카드에 표시되는 부분만 그리려면 각 카드의 자르기 영역을 지정합니다. 예를 들어 아래 다이어그램에서 클리핑 직사각형이 이미지에 적용되면 이 직사각형 내부의 부분만 표시됩니다.

클리핑 영역은 일반적으로 직사각형이지만 도형이나 도형, 조합(텍스트 포함)일 수 있습니다. 클리핑 영역 내의 리전을 포함할지 또는 제외할지 지정할 수도 있습니다. 예를 들어 원형 클리핑 영역을 만들어 원 밖에 있는 요소만 표시할 수 있습니다.

이 Codelab에서는 다양한 클리핑 방법을 실험해 봅니다.

기본 요건

다음을 잘 알고 있어야 합니다.

  • Activity로 앱을 만들고 Android 스튜디오를 사용하여 실행하는 방법
  • Canvas를 만들고 그리는 방법
  • 맞춤 View를 만들고 onDraw()onSizeChanged()를 재정의하는 방법

학습할 내용

  • Canvas에 그리기 위해 객체를 자르는 방법
  • 캔버스의 그리기 상태를 저장하고 복원하는 방법
  • 캔버스와 텍스트에 변환을 적용하는 방법

실습할 내용

  • 화면에 잘린 모양을 그리는 다양한 방법 및 이러한 도형의 공개 상태에 관한 결과를 보여주는 앱을 만듭니다.
  • 또한 왜곡된 왜곡된 텍스트를 그립니다.

ClippingExample 앱은 도형을 사용하고 결합하여 어떻게 캔버스의 어느 부분이 뷰에 표시될지 지정하는 방법을 보여줍니다. 최종 앱은 아래 스크린샷과 같이 표시됩니다.

이 앱은 처음부터 빌드하게 되므로 프로젝트를 설정하고, 측정기준과 문자열을 정의하고, 일부 변수를 선언해야 합니다.

1단계: ClippingExample 프로젝트 만들기

  1. Empty Activity 템플릿을 사용하여 ClippingExample라는 Kotlin 프로젝트를 만듭니다. 패키지 이름 접두사에는 com.example.android를 사용합니다.
  2. MainActivity.kt를 엽니다.
  3. onCreate() 메서드에서 기본 콘텐츠 뷰를 바꾸고 콘텐츠 뷰를 새 ClippedView 인스턴스로 설정합니다. 다음에 만들 클리핑 예시의 맞춤 뷰입니다.
setContentView(ClippedView(this))
  1. MainActivity.kt와 같은 수준에서 View를 확장하는 ClippedView라는 맞춤 뷰의 새 Kotlin 파일과 클래스를 만듭니다. 아래에 서명을 제공합니다. 나머지 작업은 이 ClippedView 내에 있습니다. @JvmOverloads 주석은 기본 매개변수 값을 대체하는 이 함수의 오버로드를 Kotlin 컴파일러에 생성하도록 지시합니다.
class ClippedView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}

2단계: 측정기준 및 문자열 리소스 추가

  1. res/values/dimens.xml의 새 리소스 파일에서 잘린 보기에 사용할 크기를 정의합니다. 이 기본 크기는 작은 화면에 맞게 하드코딩되고 크기가 조정됩니다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">90dp</dimen>
   <dimen name="clipRectBottom">90dp</dimen>
   <dimen name="clipRectTop">0dp</dimen>
   <dimen name="clipRectLeft">0dp</dimen>

   <dimen name="rectInset">8dp</dimen>
   <dimen name="smallRectOffset">40dp</dimen>

   <dimen name="circleRadius">30dp</dimen>
   <dimen name="textOffset">20dp</dimen>
   <dimen name="strokeWidth">4dp</dimen>

   <dimen name="textSize">18sp</dimen>
</resources>

앱이 더 큰 화면에 적합하게 표시되도록 하려면 (더 쉽게 세부정보를 볼 수 있도록) 큰 화면에만 적용되는 더 큰 값으로 dimens 파일을 만들면 됩니다.

  1. Android 스튜디오에서 values 폴더를 마우스 오른쪽 버튼으로 클릭하고 New > Values resource file을 선택합니다.
  2. New Resource File 대화상자에서 dimens 파일을 호출합니다. Available qualifiers에서 Smallest Screen Width를 선택하고 >> 버튼을 클릭하여 Chosen qualifiers에 추가합니다. 가장 작은 화면 너비 상자에 480을 입력하고 확인을 클릭합니다.

  1. 파일은 아래와 같이 값 폴더에 표시됩니다.

  1. 파일이 보이지 않으면 앱의 프로젝트 파일 뷰로 전환합니다. 새 파일의 전체 경로는 아래와 같습니다. ClippingExample/app/src/main/res/values-sw480dp/dimens.xml

  1. values-sw480dp/dimens.xml 파일의 기본 콘텐츠를 아래 크기로 바꿉니다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">120dp</dimen>
   <dimen name="clipRectBottom">120dp</dimen>

   <dimen name="rectInset">10dp</dimen>
   <dimen name="smallRectOffset">50dp</dimen>

   <dimen name="circleRadius">40dp</dimen>
   <dimen name="textOffset">25dp</dimen>
   <dimen name="strokeWidth">6dp</dimen>
</resources>
  1. strings.xml에 다음 문자열을 추가합니다. 캔버스에 텍스트를 표시하는 데 사용됩니다.
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>

3단계: Paint 및 Path 객체 생성 및 초기화

  1. 프로젝트의 Android 뷰로 다시 전환합니다.
  2. ClippedView에서 그릴 Paint 변수를 정의합니다. 앤티앨리어싱을 사용 설정하고 아래와 같이 측정기준에 정의된 획 너비와 텍스트 크기를 사용합니다.
private val paint = Paint().apply {
   // Smooth out edges of what is drawn without affecting shape.
   isAntiAlias = true
   strokeWidth = resources.getDimension(R.dimen.strokeWidth)
   textSize = resources.getDimension(R.dimen.textSize)
}
  1. ClippedView에서 그려진 항목의 경로를 로컬에 저장하도록 Path를 만들고 초기화합니다. android.graphics.Path를 가져옵니다.
private val path = Path()

4단계: 도형 설정

이 앱에서는 여러 행으로 잘린 도형 여러 행과 열을 표시합니다.

공통점이 다음과 같습니다.

  • 컨테이너 역할을 하는 큰 직사각형 (정사각형)
  • 대형 직사각형의 대각선
  • 짧은 텍스트 문자열

이 단계에서는 나중에 이러한 도형을 사용할 때 한 번만 가져오면 되도록 이러한 도형의 크기를 설정합니다.

  1. ClippedViewpath 아래에 전체 도형 세트를 자르는 직사각형의 치수 변수를 추가합니다.
private val clipRectRight = resources.getDimension(R.dimen.clipRectRight)
private val clipRectBottom = resources.getDimension(R.dimen.clipRectBottom)
private val clipRectTop = resources.getDimension(R.dimen.clipRectTop)
private val clipRectLeft = resources.getDimension(R.dimen.clipRectLeft)
  1. 직사각형의 인셋 및 작은 직사각형의 오프셋에 대한 변수를 추가합니다.
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)
  1. 원의 반경에 대한 변수를 추가합니다. 직사각형 안에 그려진 원의 반경입니다.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
  1. 직사각형 안에 그려지는 텍스트의 오프셋과 텍스트 크기를 추가합니다.
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)

4단계: 행 및 열 위치 설정

이 앱의 도형은 위에서 설정한 측정기준의 값에 따라 열 4개와 행 4개로 표시됩니다. 계산은 이 Codelab에서 다루지 않지만, 이 단계에서 제공하는 코드에 복사할 때 살펴보세요.

  1. 두 열의 좌표를 설정합니다.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
  1. 변환된 텍스트의 최종 행을 비롯하여 각 행의 좌표를 추가합니다.
private val rowOne = rectInset
private val rowTwo = rowOne + rectInset + clipRectBottom
private val rowThree = rowTwo + rectInset + clipRectBottom
private val rowFour = rowThree + rectInset + clipRectBottom
private val textRow = rowFour + (1.5f * clipRectBottom)
  1. 앱을 실행합니다. 앱이 앱 이름 아래에 빈 흰색 화면으로 열립니다.

onDraw()에서는 메서드를 호출하여 아래 앱 스크린샷과 같이 잘린 직사각형 7개를 그립니다. 직사각형은 모두 동일한 방식으로 그려집니다. 유일한 차이점은 정의된 클리핑 영역과 화면의 위치입니다.

직사각형을 그리는 데 사용되는 알고리즘은 아래 다이어그램과 설명에 따라 작동합니다. 요약하면 Canvas의 원점을 이동하여 일련의 직사각형을 그립니다. 개념적으로는 다음 단계로 구성됩니다.

(1) 먼저 직사각형을 그릴 위치로 Canvas을 변환합니다. 즉, 다음 직사각형을 비롯해 모든 도형을 그려야 하는 위치를 계산하는 대신 Canvas 원점, 즉 좌표계를 이동합니다.

(2) 그런 다음 캔버스의 새 원점에 직사각형을 그립니다. 즉, 변환된 좌표계와 동일한 위치에 도형을 그립니다. 이는 훨씬 더 간단하고 약간 더 효율적입니다.

(3) 마지막으로 Canvas를 원래 Origin로 복원합니다.

구현하는 알고리즘은 다음과 같습니다.

  1. onDraw()에서 함수를 호출하여 Canvas를 회색 배경 색상으로 채우고 원래 도형을 그립니다.
  2. 잘린 각 직사각형 및 그릴 텍스트에 대한 함수를 호출합니다.

각 직사각형 또는 텍스트에 대해 다음 단계를 따르세요.

  1. 초기 상태로 재설정할 수 있도록 Canvas의 현재 상태를 저장합니다.
  2. 캔버스의 Origin를 그리려는 위치로 변환합니다.
  3. 클리핑 도형 및 경로를 적용합니다.
  4. 직사각형이나 텍스트를 그립니다.
  5. Canvas의 상태를 복원합니다.

단계: onDraw() 재정의

  1. 아래 코드와 같이 onDraw()을 재정의합니다. 그리는 각 도형마다 함수를 호출합니다. 이 함수는 나중에 구현합니다.
 override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawBackAndUnclippedRectangle(canvas)
        drawDifferenceClippingExample(canvas)
        drawCircularClippingExample(canvas)
        drawIntersectionClippingExample(canvas)
        drawCombinedClippingExample(canvas)
        drawRoundedRectangleClippingExample(canvas)
        drawOutsideClippingExample(canvas)
        drawSkewedTextExample(canvas)
        drawTranslatedTextExample(canvas)
        // drawQuickRejectExample(canvas)
    }
  1. 코드가 계속 컴파일될 수 있도록 각 그리기 함수에 스텁을 만듭니다. 아래의 코드를 복사할 수 있습니다.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
}
private fun drawDifferenceClippingExample(canvas: Canvas){
}
private fun drawCircularClippingExample(canvas: Canvas){
}
private fun drawIntersectionClippingExample(canvas: Canvas){
}
private fun drawCombinedClippingExample(canvas: Canvas){
}
private fun drawRoundedRectangleClippingExample(canvas: Canvas){
}
private fun drawOutsideClippingExample(canvas: Canvas){
}
private fun drawTranslatedTextExample(canvas: Canvas){
}
private fun drawSkewedTextExample(canvas: Canvas){
}
private fun drawQuickRejectExample(canvas: Canvas){
}

앱은 동일한 직사각형을 7번 그리며 첫 번째는 자르지 않고 여섯 번 그리고 다양한 클리핑 경로를 적용합니다. drawClippedRectangle() 메서드는 아래와 같이 직사각형 하나를 그리기 위한 코드를 결정합니다.

1단계: drawClippedRectangle() 메서드 만들기

  1. Canvas 유형의 canvas 인수를 사용하는 drawClippedRectangle() 메서드를 만듭니다.
private fun drawClippedRectangle(canvas: Canvas) {
}
  1. drawClippedRectangle() 메서드 내에서 전체 도형의 클리핑 직사각형의 경계를 설정합니다. 정사각형만 그리는 것을 제한하는 클리핑 직사각형을 적용합니다.
canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
)

Canvas.clipRect(...) 메서드는 향후 그리기 작업이 쓸 수 있는 화면의 영역을 줄입니다. 클리핑 경계를 현재 클리핑 직사각형의 공간 교차로와 clipRect()으로 전달된 직사각형으로 설정합니다. clipRect() 메서드에는 지역에 따라 다른 형식을 허용하고 클리핑 직사각형에서 다른 작업을 허용하는 다양한 변형이 있습니다.

  1. canvas를 흰색으로 채웁니다. 물론입니다. 직사각형 전체가 그려지지 않으므로 전체 캔버스가 잘립니다. 클리핑 직사각형으로 인해 클리핑 직사각형으로 정의된 영역만 채워져 흰색 직사각형이 생성됩니다. 노출 영역 나머지는 회색으로 유지됩니다.
canvas.drawColor(Color.WHITE)
  1. 색상을 빨간색으로 변경하고 클리핑 직사각형 안에 대각선을 그립니다.
paint.color = Color.RED
canvas.drawLine(
   clipRectLeft,clipRectTop,
   clipRectRight,clipRectBottom,paint
)
  1. 색상을 녹색으로 설정하고 클리핑 직사각형 안에 원을 그립니다.
paint.color = Color.GREEN
canvas.drawCircle(
   circleRadius,clipRectBottom - circleRadius,
   circleRadius,paint
)
  1. 색상을 파란색으로 설정하고 클리핑 직사각형의 오른쪽 가장자리에 맞춰 텍스트를 그립니다. canvas.drawText()를 사용하여 텍스트를 그립니다.
paint.color = Color.BLUE
// Align the RIGHT side of the text with the origin.
paint.textSize = textSize
paint.textAlign = Paint.Align.RIGHT
canvas.drawText(
   context.getString(R.string.clipping),
   clipRectRight,textOffset,paint
)

2단계: drawBackAndUnclippedRectangle() 메서드 구현

  1. drawClippedRectangle() 메서드의 작동 모습을 보려면 아래와 같이 drawBackAndUnclippedRectangle() 메서드를 구현하여 잘리지 않은 첫 번째 직사각형을 그립니다. canvas을 저장하고 첫 번째 행과 열 위치로 변환하고 drawClippedRectangle()를 호출하여 그린 다음 canvas를 이전 상태로 복원합니다.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
   canvas.drawColor(Color.GRAY)
   canvas.save()
   canvas.translate(columnOne,rowOne)
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. 앱을 실행합니다. 첫 번째 흰색 직사각형에는 원, 빨간색 선, 텍스트가 회색 배경과 함께 표시됩니다.

클리핑 예시 메서드에서 다양한 클리핑 영역 조합을 적용하여 그래픽 효과를 달성하고 클리핑 영역을 결합하여 필요한 모양을 만드는 방법을 알아봅니다.

이러한 각 메서드는 동일한 패턴을 따릅니다.

  1. 캔버스의 현재 상태 저장: canvas.save()

활동 컨텍스트는 그리기 상태 스택을 유지합니다. 그리기 상태는 현재 변환 매트릭스와 현재 클리핑 영역으로 구성됩니다. 현재 상태를 저장하고, 그리기 상태를 변경하는 작업 (예: 캔버스 번역 또는 회전)을 수행한 다음 저장된 그리기 상태를 복원할 수 있습니다. (참고: git의 'stash" 명령어'와 같습니다.)

그림에 변환이 포함된 경우 변환을 반대로 하여 체인을 실행취소하거나 실행취소하면 오류가 발생하기 쉽습니다. 예를 들어 번역, 확대, 회전을 사용하면 빠르게 복잡해집니다. 대신 캔버스 상태를 저장하고, 변환을 적용하고 그리기를 적용한 다음 이전 상태를 복원합니다.

예를 들어 클리핑 영역을 정의하고 해당 상태를 저장할 수 있습니다. 그런 다음 캔버스를 번역하고 클리핑 영역을 추가한 다음 회전합니다. 몇 가지 그리기를 수행하면 원래 클리핑 상태를 복원할 수 있습니다. 아래 다이어그램과 같이 다른 변환 및 왜곡된 변환을 실행할 수 있습니다.

  1. 캔버스의 출처를 행/열 좌표(canvas.translate())로 변환합니다.

그리려는 모든 요소를 이동하는 것보다 캔버스의 출처를 이동하거나 새 좌표계에 같은 그림을 그리는 것이 훨씬 간단합니다. (도움말: 요소 회전에도 동일한 기법을 사용할 수 있습니다.)

  1. path에 변환을 적용합니다(있는 경우).
  2. 클리핑 적용: canvas.clipPath(path)
  3. 도형 그리기: drawClippedRectangle() or drawText()
  4. 이전 캔버스 상태 복원: canvas.restore()

1단계: drawDifferenceClippingExample(캔버스) 구현

두 클리핑 직사각형의 차이를 사용하여 디지털 액자 효과를 만드는 두 번째 직사각형을 그리는 코드를 추가합니다.

다음 작업을 하는 코드를 사용합니다.

  1. 캔버스를 저장합니다.
  2. 캔버스의 출처를 첫 번째 직사각형의 첫 번째 행, 두 번째 열, 첫 번째 직사각형의 오른쪽에 공백으로 변환합니다.
  3. 클리핑 직사각형 두 개를 적용합니다. DIFFERENCE 연산자는 첫 번째 직사각형에서 두 번째 직사각형을 뺍니다.
  1. drawClippedRectangle() 메서드를 호출하여 수정된 캔버스를 그립니다.
  2. 캔버스 상태를 복원합니다.
private fun drawDifferenceClippingExample(canvas: Canvas) {
   canvas.save()
   // Move the origin to the right for the next rectangle.
   canvas.translate(columnTwo,rowOne)
   // Use the subtraction of two clipping rectangles to create a frame.
   canvas.clipRect(
       2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .DIFFERENCE) was deprecated in API level 26. The recommended
   // alternative method is clipOutRect(float, float, float, float),
   // which is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O){
       canvas.clipRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset,
            Region.Op.DIFFERENCE
       )
   } else {
       canvas.clipOutRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. 앱을 실행하면 다음과 같이 표시됩니다.

2단계: drawCircularClippingExample(Canvas) 구현

다음으로, 코드를 추가하여 원형 경로에서 만든 원형 클리핑 영역을 사용하는 직사각형을 그립니다. 그러면 원이 삭제되어 그려지지 않으며 대신 회색 배경이 표시됩니다.

private fun drawCircularClippingExample(canvas: Canvas) {

   canvas.save()
   canvas.translate(columnOne, rowTwo)
   // Clears any lines and curves from the path but unlike reset(),
   // keeps the internal data structure for faster reuse.
   path.rewind()
   path.addCircle(
       circleRadius,clipRectBottom - circleRadius,
       circleRadius,Path.Direction.CCW
   )
   // The method clipPath(path, Region.Op.DIFFERENCE) was deprecated in
   // API level 26. The recommended alternative method is
   // clipOutPath(Path), which is currently available in
   // API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipPath(path, Region.Op.DIFFERENCE)
   } else {
       canvas.clipOutPath(path)
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

3단계: drawIntersectionClippingExample(캔버스) 구현

그런 다음 코드를 추가하여 두 번째 행과 열에 있는 클리핑 직사각형 두 개를 교차합니다.

화면 해상도에 따라 이 영역의 모양이 다릅니다. smallRectOffset 영역의 실험을 통해 표시되는 영역의 크기를 변경합니다. smallRectOffset가 작을수록 화면 영역이 더 넓어집니다.

private fun drawIntersectionClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowTwo)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight - smallRectOffset,
       clipRectBottom - smallRectOffset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .INTERSECT) was deprecated in API level 26. The recommended
   // alternative method is clipRect(float, float, float, float), which
   // is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom,
           Region.Op.INTERSECT
       )
   } else {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

4단계: drawCombinedClippingExample(캔버스) 구현

다음으로 도형, 원, 직사각형을 결합하고 경로를 지정하여 클리핑 영역을 정의합니다.

private fun drawCombinedClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne, rowThree)
   path.rewind()
   path.addCircle(
       clipRectLeft + rectInset + circleRadius,
       clipRectTop + circleRadius + rectInset,
       circleRadius,Path.Direction.CCW
   )
   path.addRect(
       clipRectRight / 2 - circleRadius,
       clipRectTop + circleRadius + rectInset,
       clipRectRight / 2 + circleRadius,
       clipRectBottom - rectInset,Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

5단계: drawRoundedRectangleClippingExample(캔버스) 구현

다음으로, 일반적으로 사용되는 클리핑 모양인 둥근 직사각형을 추가합니다.

  1. 최상위 수준에서 직사각형 변수를 만들고 초기화합니다. RectF는 부동 소수점 내에 직사각형 좌표를 보유한 클래스입니다.
private var rectF = RectF(
   rectInset,
   rectInset,
   clipRectRight - rectInset,
   clipRectBottom - rectInset
)
  1. drawRoundedRectangleClippingExample() 함수를 구현합니다. addRoundRect() 함수는 직사각형, 모서리 반지름의 x 및 y 값, 둥근 사각형의 윤곽을 구는 방향을 사용합니다. Path.Direction는 경로에 추가된 닫힌 도형 (예: 직사각형, 타원)의 방향을 지정합니다. CCW는 시계 반대 방향입니다.
private fun drawRoundedRectangleClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowThree)
   path.rewind()
   path.addRoundRect(
       rectF,clipRectRight / 4,
       clipRectRight / 4, Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

6단계: drawOutsideClippingExample(캔버스) 구현

클리핑 직사각형의 인셋을 두 배로 하여 직사각형 주변을 자릅니다.

private fun drawOutsideClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne,rowFour)
   canvas.clipRect(2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset)
   drawClippedRectangle(canvas)
   canvas.restore()
}

7단계: drawTranslationdTextExample(캔버스) 구현

그리기 텍스트는 다른 도형과 크게 다르지 않으며 텍스트에 변환을 적용할 수 있습니다. 예를 들어 캔버스를 번역하고 텍스트를 그려 텍스트를 번역할 수 있습니다.

  1. 아래 함수를 구현하세요.
private fun drawTranslatedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.GREEN
   // Align the RIGHT side of the text with the origin.
   paint.textAlign = Paint.Align.LEFT
   // Apply transformation to canvas.
   canvas.translate(columnTwo,textRow)
   // Draw text.
   canvas.drawText(context.getString(R.string.translated),
       clipRectLeft,clipRectTop,paint)
   canvas.restore()
}
  1. 앱을 실행하여 번역된 텍스트를 확인합니다.

8단계: drawSkewedTextExample(캔버스) 구현

텍스트를 왜곡할 수도 있습니다. 즉, 다양한 방식으로 왜곡합니다.

  1. 아래 ClippedView에서 함수를 만듭니다.
private fun drawSkewedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.YELLOW
   paint.textAlign = Paint.Align.RIGHT
   // Position text.
   canvas.translate(columnTwo, textRow)
   // Apply skew transformation.
   canvas.skew(0.2f, 0.3f)
   canvas.drawText(context.getString(R.string.skewed),
       clipRectLeft, clipRectTop, paint)
   canvas.restore()
}
  1. 앱을 실행하여 번역된 텍스트 앞에 그려진 왜곡된 텍스트를 확인합니다.

quickReject() Canvas 메서드를 사용하면 모든 변환이 적용된 후 지정된 직사각형이나 경로가 현재 표시된 영역 밖에 완전히 표시될지 확인할 수 있습니다.

quickReject() 메서드는 더 복잡한 그리기를 구성하고 최대한 빨리 사용해야 할 때 매우 유용합니다. quickReject()를 사용하면 전혀 그릴 필요가 없는 객체를 효율적으로 결정할 수 있으며 자체 교차 로직을 작성할 필요가 없습니다.

  • quickReject() 메서드는 직사각형이나 경로가 화면에 전혀 표시되지 않으면 true을 반환합니다. 부분 중복의 경우에도 자체 검사가 필요합니다.
  • EdgeTypeAA(AntiAliased: 안티앨리어싱될 수 있으므로 반올림하여 가장자리 처리) 또는 BW(흑백: 가장 가까운 픽셀 경계로 반올림하여 가장자리 처리)(가장 가까운 픽셀로 반올림)입니다.

quickReject()에는 여러 버전이 있으며 문서에서도 찾을 수 있습니다.

boolean

quickReject(float left, float top, float right, float bottom, Canvas.EdgeType type)

boolean

quickReject(RectF rect, Canvas.EdgeType type)

boolean

quickReject(경로 path, Canvas.EdgeType type)

이 연습에서는 이전과 같이 새 행, 텍스트 아래 및 clipRect 안에 그립니다.

  • 먼저 clipRect와 겹치는 직사각형 inClipRectangle를 사용하여 quickReject()를 호출합니다. 따라서 quickReject()이 false를 반환하고 clipRectBLACK로 채워지고 inClipRectangle 직사각형이 그려집니다.

  • 그런 다음 코드를 notInClipRectangle와 함께 사용하여 quickReject()를 호출합니다. 이제 quickReject()가 true를 반환하고 clipRectWHITE로 채워지고 notInClipRectangle가 그려지지 않습니다.

복잡한 그림이 있는 경우 클리핑 영역 밖에서 완전히 다른 모양이고 어떤 도형은 클리핑 영역 내에 일부 또는 완전히 포함되어 있으므로 추가 계산과 그리기를 수행해야 할 수 있습니다.

단계: QuickReject() 실험

  1. 최상위 수준에서 추가 행의 y 좌표에 대한 변수를 만듭니다.
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. 다음 drawQuickRejectExample() 함수를 ClippedView에 추가합니다. quickReject()를 사용해야 하는 모든 정보가 포함되어 있으므로 코드를 읽으세요.
private fun drawQuickRejectExample(canvas: Canvas) {
   val inClipRectangle = RectF(clipRectRight / 2,
       clipRectBottom / 2,
       clipRectRight * 2,
       clipRectBottom * 2)

   val notInClipRectangle = RectF(RectF(clipRectRight+1,
       clipRectBottom+1,
       clipRectRight * 2,
       clipRectBottom * 2))

   canvas.save()
   canvas.translate(columnOne, rejectRow)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
   )
   if (canvas.quickReject(
           inClipRectangle, Canvas.EdgeType.AA)) {
       canvas.drawColor(Color.WHITE)
   }
   else {
       canvas.drawColor(Color.BLACK)
       canvas.drawRect(inClipRectangle, paint
       )
   }
       canvas.restore()
}
  1. onDraw()에서 drawQuickRejectExample() 호출의 주석 처리를 삭제합니다.
  2. 앱을 실행하면 두 개의 직사각형이 겹치므로 채워진 클리핑 영역인 검은색 직사각형과 inClipRectangle 일부가 표시됩니다. 따라서 두 직사각형이 두 개이므로 quickReject()falseinClipRectangle을 반환합니다.

  1. drawQuickRejectExample()에서 quickReject()를 실행하도록 코드를 변경합니다. notInClipRectangle.이제 quickReject()true를 반환하고 클리핑 영역이 흰색으로 채워집니다.

완성된 Codelab의 코드를 다운로드합니다.

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-clipping


또는 저장소를 ZIP 파일로 다운로드하여 압축을 풀고 Android 스튜디오에서 열 수 있습니다.

ZIP 파일 다운로드

  • 활동의 ContextCanvas의 변환 및 클리핑 영역을 보존하는 상태를 유지합니다.
  • 캔버스의 원래 상태로 그리려면 canvas.save()canvas.restore()를 사용합니다.
  • 캔버스에 여러 도형을 그리려면 위치를 계산하거나 그림 표면의 출처를 이동 (번역)하면 됩니다. 후자를 사용하면 그리기 시퀀스의 반복을 위한 유틸리티 메서드를 더 쉽게 만들 수 있습니다.
  • 클리핑 영역은 어떤 형태나 도형 또는 경로의 조합일 수 있습니다.
  • 클리핑 영역을 추가, 빼기 및 교차하면 필요한 영역을 정확하게 얻을 수 있습니다.
  • 캔버스를 변환하여 텍스트에 변환을 적용할 수 있습니다.
  • quickReject() Canvas 메서드를 사용하면 지정된 직사각형이나 경로가 현재 표시된 지역 외부에 완전히 위치하는지 확인할 수 있습니다.

Udacity 과정:

Android 개발자 문서:

Android 프레임워크가 화면에 그려지는 방식에 관한 자세한 설명은 그래픽 아키텍처 시리즈를 참고하세요.

이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 통해 작업하는 학생들의 숙제 과제가 나와 있습니다. 강사는 다음을 처리합니다.

  • 필요한 경우 과제를 할당합니다.
  • 학생에게 과제 과제를 제출하는 방법을 알려주세요.
  • 과제 과제를 채점합니다.

강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 다른 적절한 숙제를 할당해도 좋습니다.

이 Codelab을 직접 학습하고 있다면 언제든지 숙제를 통해 지식을 확인해 보세요.

답변

질문 1

도형을 그리는 것을 효율적으로 제외하기 위해 호출하는 메서드는 무엇인가요?

excludeFromDrawing()

quickReject()

onDraw()

clipRect()

질문 2

Canvas.save()Canvas.restore()에서는 어떤 정보를 저장하고 복원하나요?

▢ 색상, 선 너비 등

▢ 현재 변환만

▢ 현재 변환 및 클리핑 영역

▢ 현재 클리핑 영역만

질문 3

Paint.Align에서는 다음을 지정합니다.

▢ 다음과 같은 그림 모양을 정렬하는 방법

▢ 텍스트를 가져오는 출처 측

▢ 클리핑 영역에서 정렬된 위치

▢ 원본에서 정렬되는 텍스트 측면

이 과정의 다른 Codelab에 관한 링크는 Kotlin Codelab의 고급 Android 방문 페이지를 참고하세요.