【Android】MotionLayout을 만져보세요
17358 단어 안드로이드Android 앱MotionLayout안드로이드 개발
MotionLayout이란?
View의 애니메이션을 관리할 수 있는 ViewGroup입니다.
ConstraintLayout의 서브 클래스에서 ConstraintLayout을 사용하는 레이아웃이라면 MotionLayout으로 완전히 바꿀 수 있습니다.
※MotionLayout은 API 레벨 14와의 하위 호환성이 있습니다.
소개
gradle에 종속성을 추가합니다.
MotionLayout은 ConstraintLayoutのライブラリの2.0.0から導入
되었기 때문에 ConstraintLayout의 버전을 올려야합니다.
※2020/3/5의 시점에서의 최신 버전은 2.0.0-beta4였습니다.
build.gradle(app)// AndroidXを導入している場合はこちら
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
// それ以外はこちら
implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta4'
MotionLayout에서 이용하는 애니메이션 파일※1을 넣기 위한 XML 폴더※2를 작성합니다.
※1 엄밀하게는 애니메이션 개시시와 종료시의 View 상태를 기술한 파일입니다.
※2 AndroidStudio의 메뉴에서, File > New > Folder > XML Resource Folder로 작성.
만져보세요
정교한 레이아웃을 만드는 시간이 없으므로 기본 activity_main.xml
에 MotionLayout을 적응해보십시오.
activity_main.xml<!-- ConstraintLayoutからMotionLayoutに置き換えました。 -->
<androidx.constraintlayout.motion.widget.MotionLayout
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"
tools:context=".MainActivity"
>
<!-- アニメーションさせたいViewにidをつけます。 -->
<TextView
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.motion.widget.MotionLayout>
그런 다음 res/xml/
에 애니메이션 파일을 만듭니다.
res/xml/motion_scene_main.xml<!-- MotionScene※3タグで大枠を作ります。xmlns:motion="~"を忘れずに。 -->
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
>
<!-- 開始、終了アニメーションの指定と実行時間を記述します。 -->
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000"
>
<!-- トリガーとなるアクションとViewのidを紐付けます。 -->
<OnClick motion:targetId="@id/main_text_view"/>
</Transition>
<!-- アニメーション開始時のViewの状態を記述します。 -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
/>
</ConstraintSet>
<!-- アニメーション終了時のViewの状態を記述します。 -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
/>
</ConstraintSet>
</MotionScene>
※3 xmlns:motion="~"를 포함하면 MotionScene이라는 명명이 아니라 어떤 이름을 붙여도 인식해 줍니다.
마지막으로 MotionLayout과 애니메이션 파일을 연결합니다.
activity_main.xml<androidx.constraintlayout.motion.widget.MotionLayout
〜 省略 〜
app:layoutDescription="@xml/motion_scene_main"
>
기기에서 확인하려고합니다.
텍스트를 탭하면 제대로 애니메이션하고 있네요.
MotionLayout은 View 자체를 애니메이션화할 뿐만 아니라 View 매개변수를 동적으로 변경할 수 있습니다.
애니메이션 파일에 약간의 손을 추가해 봅니다.
res/xml/motion_scene_main.xml<?xml version="1.0" encoding="utf-8"?>
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
>
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000"
>
<OnClick motion:targetId="@id/main_text_view" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
>
<!-- アニメーション開始時のテキストの色を指定します。 -->
<CustomAttribute
motion:attributeName="textColor"
motion:customColorValue="#ff0000"
/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
>
<!-- アニメーション終了時のテキストの色を指定します。 -->
<CustomAttribute
motion:attributeName="textColor"
motion:customColorValue="#0000ff"
/>
</Constraint>
</ConstraintSet>
</MotionScene>
CustomAttribute
라는 태그는, attributeName 에 지정한 View 의 파라미터를 애니메이션에 맞추어 변경해 줍니다.
색상 변경 외에도 애니메이션에 맞게 뷰를 변형하거나 이미지를 크로스 페이드할 수 있습니다.
CustomAttribute를 복수 지정하는 것도 물론 가능합니다.
여기에서는 특히 만지지 않으므로 신경이 쓰이는 분은 조사해 보세요.
추가 1
위에서는 애니메이션 파일에 트리거가 되는 View를 연결하고 있었습니다만, 실제의 프로젝트에서는, 코드로 지정하는 편이 일반적이라고 생각합니다.
res/xml/motion_scene_main.xml<!-- ここは削除して構いません。 -->
<OnClick motion:targetId="@id/main_text_view" />
MainActivity.ktclass MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val motionLayout = findViewById<MotionLayout>(R.id.main_motion_layout)
// トリガーとなるViewのクリック時に、指定したid※4のアニメーションを開始します。
findViewById<TextView>(R.id.main_text_view).setOnClickListener {
// 現在のアニメーションの状態を判定して、開始するアニメーションを決めます。
if (motionLayout.currentState == R.id.start) {
motionLayout.transitionToEnd()
} else {
motionLayout.transitionToStart()
}
}
}
}
※4 애니메이션 파일로 정의한 ConstraintSet의 id입니다.
추가 2
요구 사항에 따라 애니메이션 진행 상황을 감지하고 싶을 수도 있습니다.
그럴 때는 MotionLayout.TransitionListener
를 설정하면 콜백을 얻을 수 있습니다.
MainActivity.ktmotionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
// トリガーした時に呼ばれます。
}
override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
// アニメーション開始時に呼ばれます。
}
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
// アニメーション中に呼ばれます。
}
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
// アニメーション終了時に呼ばれます。
}
})
감상
MotionLayout의 매력은 거의 기존의 구현에 영향을 미치지 않고, 선형 보완의 매끄러운 애니메이션으로 간편하게 풍부한 표현을 할 수 있는 곳에 있다고 생각했습니다. 또한 학습 비용이 비교적 낮은 것도 ◎.
참고
Reference
이 문제에 관하여(【Android】MotionLayout을 만져보세요), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/AtsushiUemura/items/9b685bbfb968fe58b8f6
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
gradle에 종속성을 추가합니다.
MotionLayout은
ConstraintLayoutのライブラリの2.0.0から導入
되었기 때문에 ConstraintLayout의 버전을 올려야합니다.※2020/3/5의 시점에서의 최신 버전은 2.0.0-beta4였습니다.
build.gradle(app)
// AndroidXを導入している場合はこちら
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
// それ以外はこちら
implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta4'
MotionLayout에서 이용하는 애니메이션 파일※1을 넣기 위한 XML 폴더※2를 작성합니다.
※1 엄밀하게는 애니메이션 개시시와 종료시의 View 상태를 기술한 파일입니다.
※2 AndroidStudio의 메뉴에서, File > New > Folder > XML Resource Folder로 작성.
만져보세요
정교한 레이아웃을 만드는 시간이 없으므로 기본 activity_main.xml
에 MotionLayout을 적응해보십시오.
activity_main.xml<!-- ConstraintLayoutからMotionLayoutに置き換えました。 -->
<androidx.constraintlayout.motion.widget.MotionLayout
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"
tools:context=".MainActivity"
>
<!-- アニメーションさせたいViewにidをつけます。 -->
<TextView
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.motion.widget.MotionLayout>
그런 다음 res/xml/
에 애니메이션 파일을 만듭니다.
res/xml/motion_scene_main.xml<!-- MotionScene※3タグで大枠を作ります。xmlns:motion="~"を忘れずに。 -->
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
>
<!-- 開始、終了アニメーションの指定と実行時間を記述します。 -->
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000"
>
<!-- トリガーとなるアクションとViewのidを紐付けます。 -->
<OnClick motion:targetId="@id/main_text_view"/>
</Transition>
<!-- アニメーション開始時のViewの状態を記述します。 -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
/>
</ConstraintSet>
<!-- アニメーション終了時のViewの状態を記述します。 -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
/>
</ConstraintSet>
</MotionScene>
※3 xmlns:motion="~"를 포함하면 MotionScene이라는 명명이 아니라 어떤 이름을 붙여도 인식해 줍니다.
마지막으로 MotionLayout과 애니메이션 파일을 연결합니다.
activity_main.xml<androidx.constraintlayout.motion.widget.MotionLayout
〜 省略 〜
app:layoutDescription="@xml/motion_scene_main"
>
기기에서 확인하려고합니다.
텍스트를 탭하면 제대로 애니메이션하고 있네요.
MotionLayout은 View 자체를 애니메이션화할 뿐만 아니라 View 매개변수를 동적으로 변경할 수 있습니다.
애니메이션 파일에 약간의 손을 추가해 봅니다.
res/xml/motion_scene_main.xml<?xml version="1.0" encoding="utf-8"?>
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
>
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000"
>
<OnClick motion:targetId="@id/main_text_view" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
>
<!-- アニメーション開始時のテキストの色を指定します。 -->
<CustomAttribute
motion:attributeName="textColor"
motion:customColorValue="#ff0000"
/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
>
<!-- アニメーション終了時のテキストの色を指定します。 -->
<CustomAttribute
motion:attributeName="textColor"
motion:customColorValue="#0000ff"
/>
</Constraint>
</ConstraintSet>
</MotionScene>
CustomAttribute
라는 태그는, attributeName 에 지정한 View 의 파라미터를 애니메이션에 맞추어 변경해 줍니다.
색상 변경 외에도 애니메이션에 맞게 뷰를 변형하거나 이미지를 크로스 페이드할 수 있습니다.
CustomAttribute를 복수 지정하는 것도 물론 가능합니다.
여기에서는 특히 만지지 않으므로 신경이 쓰이는 분은 조사해 보세요.
추가 1
위에서는 애니메이션 파일에 트리거가 되는 View를 연결하고 있었습니다만, 실제의 프로젝트에서는, 코드로 지정하는 편이 일반적이라고 생각합니다.
res/xml/motion_scene_main.xml<!-- ここは削除して構いません。 -->
<OnClick motion:targetId="@id/main_text_view" />
MainActivity.ktclass MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val motionLayout = findViewById<MotionLayout>(R.id.main_motion_layout)
// トリガーとなるViewのクリック時に、指定したid※4のアニメーションを開始します。
findViewById<TextView>(R.id.main_text_view).setOnClickListener {
// 現在のアニメーションの状態を判定して、開始するアニメーションを決めます。
if (motionLayout.currentState == R.id.start) {
motionLayout.transitionToEnd()
} else {
motionLayout.transitionToStart()
}
}
}
}
※4 애니메이션 파일로 정의한 ConstraintSet의 id입니다.
추가 2
요구 사항에 따라 애니메이션 진행 상황을 감지하고 싶을 수도 있습니다.
그럴 때는 MotionLayout.TransitionListener
를 설정하면 콜백을 얻을 수 있습니다.
MainActivity.ktmotionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
// トリガーした時に呼ばれます。
}
override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
// アニメーション開始時に呼ばれます。
}
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
// アニメーション中に呼ばれます。
}
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
// アニメーション終了時に呼ばれます。
}
})
감상
MotionLayout의 매력은 거의 기존의 구현에 영향을 미치지 않고, 선형 보완의 매끄러운 애니메이션으로 간편하게 풍부한 표현을 할 수 있는 곳에 있다고 생각했습니다. 또한 학습 비용이 비교적 낮은 것도 ◎.
참고
Reference
이 문제에 관하여(【Android】MotionLayout을 만져보세요), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/AtsushiUemura/items/9b685bbfb968fe58b8f6
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
<!-- ConstraintLayoutからMotionLayoutに置き換えました。 -->
<androidx.constraintlayout.motion.widget.MotionLayout
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"
tools:context=".MainActivity"
>
<!-- アニメーションさせたいViewにidをつけます。 -->
<TextView
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.motion.widget.MotionLayout>
<!-- MotionScene※3タグで大枠を作ります。xmlns:motion="~"を忘れずに。 -->
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
>
<!-- 開始、終了アニメーションの指定と実行時間を記述します。 -->
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000"
>
<!-- トリガーとなるアクションとViewのidを紐付けます。 -->
<OnClick motion:targetId="@id/main_text_view"/>
</Transition>
<!-- アニメーション開始時のViewの状態を記述します。 -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
/>
</ConstraintSet>
<!-- アニメーション終了時のViewの状態を記述します。 -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
/>
</ConstraintSet>
</MotionScene>
<androidx.constraintlayout.motion.widget.MotionLayout
〜 省略 〜
app:layoutDescription="@xml/motion_scene_main"
>
<?xml version="1.0" encoding="utf-8"?>
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
>
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000"
>
<OnClick motion:targetId="@id/main_text_view" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
>
<!-- アニメーション開始時のテキストの色を指定します。 -->
<CustomAttribute
motion:attributeName="textColor"
motion:customColorValue="#ff0000"
/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
>
<!-- アニメーション終了時のテキストの色を指定します。 -->
<CustomAttribute
motion:attributeName="textColor"
motion:customColorValue="#0000ff"
/>
</Constraint>
</ConstraintSet>
</MotionScene>
<!-- ここは削除して構いません。 -->
<OnClick motion:targetId="@id/main_text_view" />
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val motionLayout = findViewById<MotionLayout>(R.id.main_motion_layout)
// トリガーとなるViewのクリック時に、指定したid※4のアニメーションを開始します。
findViewById<TextView>(R.id.main_text_view).setOnClickListener {
// 現在のアニメーションの状態を判定して、開始するアニメーションを決めます。
if (motionLayout.currentState == R.id.start) {
motionLayout.transitionToEnd()
} else {
motionLayout.transitionToStart()
}
}
}
}
motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
// トリガーした時に呼ばれます。
}
override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
// アニメーション開始時に呼ばれます。
}
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
// アニメーション中に呼ばれます。
}
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
// アニメーション終了時に呼ばれます。
}
})
MotionLayout의 매력은 거의 기존의 구현에 영향을 미치지 않고, 선형 보완의 매끄러운 애니메이션으로 간편하게 풍부한 표현을 할 수 있는 곳에 있다고 생각했습니다. 또한 학습 비용이 비교적 낮은 것도 ◎.
참고
Reference
이 문제에 관하여(【Android】MotionLayout을 만져보세요), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/AtsushiUemura/items/9b685bbfb968fe58b8f6
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
Reference
이 문제에 관하여(【Android】MotionLayout을 만져보세요), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/AtsushiUemura/items/9b685bbfb968fe58b8f6텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)