하위 뷰에 대한 상대 Rect 정보 가져오기 및 덮어쓰기에서 잘라내기

18512 단어 Android

입문


Android Advent Calendar 2019의 12일째.
오늘 젓가락을 쉴 때 가장 최근의 기술을 쓰는 것이 아니라View 조작에 관한 일을 쓴다.
제목은'자손의 보기에 대한 상대적 Rect 정보를 얻고 중첩에서 잘라내기'입니다. 이번에는 Activity에서 중첩된 자체 샘플링 코드를 잘라낸 것을 바탕으로 포함된 Fragent가 가지고 있는 보기의 특정 부모 보기 그룹에서 상대적 Rect(직사각형 좌표) 정보를 얻고 조작하는 예를 설명합니다.
나는 매우 추상적인 설명이 이해하기 어렵다고 생각해서 그림으로 설명해 주었다.

액티비티가 MainFragment와 전체 액티비티를 덮어쓰는 MaskView(반투명 덮어쓰기)를 레이아웃으로 한다고 가정합니다.
※ MaskView는 독자적으로 정의된 사용자 정의 보기로 덮어쓰는 색과 특정한 Rect 범위를 투명하게 하는 방법이 있습니다.잠시 후 터치하세요.
그리고 Fragment가 가지고 있는 특정View(위 이미지에서 말한 Pic1)의, Activity로부터의 상대적인 Rect 정보를 가져와 이 좌표 범위의 MaskView를 동적 투명하게 하여'잘라내기'처럼 보일 수 있습니다.
gif 이미지는 다음과 같습니다.여기서 FAB를 눌렀을 때 첫 번째 이미지 범위의 덮어쓰기를 잘라냅니다.
또한 샘플 응용 프로그램은 아래부터 시작하십시오.

포인트는 OffsetDescendantRectToMyCoords입니다.


여기서 중요한 점은 ViewGroup의 방법offsetDescendantRectToMyCoords입니다.
Offset a rectangle that is in a descendant's coordinate space into our coordinate space.
자손 보기에 여분 등을 지정하거나 중첩된 차원wrap_content을 사용하더라도 이에 따라 부모 보기 그룹 좌표 공간의 좌표를 얻을 수 있다.
이 경우 다음과 같이 Activity 레이아웃에는 container 및 하위 main_fragment 가 있습니다.이 main_fragment 를 포함하는View의container에서 상대적인 Rect 정보를 얻으면 전체 container를 덮어쓰는 mask 에서 대상 범위를 잘라낼 수 있습니다.
레이아웃 예
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/main_fragment"
        android:name="com.example.nichiyoshi.maskclipsample.MainFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <com.example.nichiyoshi.maskclipsample.MaskView
        android:id="@+id/mask"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/fab_margin"
        app:srcCompat="@drawable/ic_scissors"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

사전 준비: 사용자 정의 MaskView


다음 MaskView는 반투명 검은색(#800000)으로 View 범위를 채우고 clipWithRect(rect: Rect) 메서드를 호출할 때 매개변수가 지정한 Rect 범위를 투명하게 다시 그립니다.
MaskView
class MaskView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {


    private val backgroundPaint = Paint().apply {
        color = Color.parseColor("#80000000")
    }

    private val clearPaint = Paint().apply {
        xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
    }

    private var clipRect: Rect? = null

    fun clipWithRect(rect: Rect) {
        clipRect = rect
        postInvalidate()
    }

    override fun draw(canvas: Canvas) {
        super.draw(canvas)
        setLayerType(LAYER_TYPE_HARDWARE, null)

        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), backgroundPaint)

        clipRect?.let { rect ->
            canvas.drawRect(rect, clearPaint)
        }
    }

}
그리고 참고할 수 있도록 허락해 주십시오이 문장.

총체적



Activity와 MainFragment에서 ViewModel을 공유하고 Activity FAB를 누르면 ViewModel의 requestViewToClip 함수를 호출합니다.이를 감시하는 MainFragment는ViewModel의 setViewToClip 함수에서 재단할 View (pic1)를 지정하고, 이를 감시하는 Activity는 아까의 offsetDescendantRectToMyCoords 함수에서container에서 상대적인 Rect 범위를 가져오고, MaskView에서 이 범위를 재단합니다.
MainActivity
class MainActivity : AppCompatActivity() {

    private val viewModel by viewModels<MainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        fab.setOnClickListener {
            viewModel.requestViewToClip()
        }

        viewModel.viewToClip.observe(this) { targetView ->
            Rect().apply {
                targetView.getDrawingRect(this)
                container.offsetDescendantRectToMyCoords(targetView, this)
                mask.clipWithRect(this)
            }
        }
    }
}
observe(this){}에 해당하는 곳에서 사용KTX.
MainViewModel
class MainViewModel: ViewModel() {

    private val _viewToClip = MutableLiveData<View>()
    val viewToClip: LiveData<View> = _viewToClip

    fun setViewToCLip(view: View) {
        _viewToClip.postValue(view)
    }

    private val _requestViewToClip = MutableLiveData<Unit>()
    val requestViewToClip: LiveData<Unit> = _requestViewToClip

    fun requestViewToClip() {
        _requestViewToClip.postValue(Unit)
    }

}
MainFragment
class MainFragment: Fragment(R.layout.fragment_main) {

    private val viewModel by activityViewModels<MainViewModel>()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        image1.scaleType = ImageView.ScaleType.FIT_CENTER
        image2.scaleType = ImageView.ScaleType.FIT_CENTER

        // loads images from free image provider "pakutaso"
        image1.load("https://www.pakutaso.com/shared/img/thumb/cat9302341_TP_V.jpg")
        image2.load("https://www.pakutaso.com/shared/img/thumb/cat9302331_TP_V.jpg")

        viewModel.requestViewToClip.observe(viewLifecycleOwner) {
            viewModel.setViewToCLip(image1)
        }
    }

}
by activityViewModels 뷰모델의 속성 삭제 입니다.
class MainFragment: Fragment(R.layout.fragment_main)에 해당하는 곳에서 사용프레임 레이아웃 Id 구조 함수.

끝내다


다소 이해하기 어려운 설명이지만 여기서 끝난다.
소스 코트는 GitHub 거예요.
https://github.com/nichiyoshi/clip_mask_sample

좋은 웹페이지 즐겨찾기