반응형

오늘은 코틀린(Kotlin)을 사용해서 파일 탐색기 만들기에 경로 확인 ToolBar을 추가해보겠습니다.

파일 탐색기를 사용해서 파일을 이동하면 현재 위치를 알 수 없는 단점을 보안하기 위해서 상단에 ToolBar를 사용해서 현재 경로를 저장할 수 있는 로직을 구현해보겠습니다.

먼저 ToolBar에 적용하기 위한 layout을 생성합니다.

layout 하단에 item_file_breadcrumb.xml 파일을 생성합니다.

Android 버전에 따라서 ConstraintLayout는 패키지 정보를 수정해야 합니다.

수정을 안할 경우 실행 시 App이 바로 종료되는 문제가 발생합니다.

<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="?selectableItemBackgroundBorderless"
    android:padding="8dp">

    <ImageView
        android:id="@+id/arrowTextView"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:src="@drawable/ic_folder_dark_24dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:tint="@color/grey"/>

    <TextView
        android:id="@+id/nameTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:layout_marginStart="4dp"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@id/arrowTextView"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Pictures" />

</androidx.constraintlayout.widget.ConstraintLayout>

ToolBar는 이미지를 출력하기 위한 ImageView 및 경로를 출력하기 위한 TextView로 구성해줍니다.

ImageView에 사용할 이미지를 Vector을 사용해서 추가합니다.

VectorDrawable는 정적 드로어블 객체로 path 및 group 객체로 구성할 수 있습니다.

https://developer.android.com/guide/topics/graphics/vector-drawable-resources?hl=ko 

 

벡터 드로어블 개요  |  Android 개발자  |  Android Developers

이 문서에서는 프레임워크 API 또는 지원 라이브러리를 통해 벡터 드로어블 리소스의 전반적인 사용을 설명합니다.

developer.android.com

drawable 하단에 ic_right_black_24dp.xml을 추가합니다.

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</vector>

vector을 사용해서 path를 구성합니다.

색상을 추가하기 위해서 colors.xml에 grey를 추가합니다.

이번에는 모든 경로를 확인하기 위한 RecyclerView.Adapter를 확장한 BreadcrumbFileAdapter 클래스를 생성합니다.

class BreadcrumbFileAdapter : RecyclerView.Adapter<BreadcrumbFileAdapter.ViewHolder>() {

    var onItemClickListener: ((FileModel) -> Unit)? = null

    var files = listOf<FileModel>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_file_breadcrumb, parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount() = files.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bindView(position)

    fun updateData(files: List<FileModel>) {
        this.files = files
        notifyDataSetChanged()
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {

        init {
            itemView.setOnClickListener(this)
        }

        override fun onClick(v: View?) {
            onItemClickListener?.invoke(files[adapterPosition])
        }

        fun bindView(position: Int) {
            val file = files[position]
            itemView.nameTextView.text = file.name
        }
    }
}

사용자가 선택한 파일 목록을 업데이트 하기 위해서 클래스 내부에 있는 RecyclerView.Adapter를 사용합니다.

이제 경로를 확인하기 위한 layout을 activity_main.xml에 추가합니다.

<com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:title="@string/app_name"
            app:titleTextColor="@color/grey">

        </androidx.appcompat.widget.Toolbar>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/breadcrumbRecyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        </androidx.recyclerview.widget.RecyclerView>

    </com.google.android.material.appbar.AppBarLayout>

Toolbar widget를 사용하기 위해서 AppBarLayout를 사용합니다.

최신 Android 버전에서는 패키지가 변경되어 패키지 수정이 필요합니다.

패키지 오류가 발생할 경우 아래 사이트를 확인해주세요.

https://believecom.tistory.com/750?category=1109462 

 

android How to solve ' java.lang.ClassNotFoundException: Didn't find class "com.google.android.material.appbar.AppBarLayout"'

Android에서 AppBarLayout을 추가하면 컴파일에는 문제가 없지만 실행 시점 오류가 발생합니다. 오류 내용은 'java.lang.ClassNotFoundException: Didn't find class "com.google.android.material.appbar.Ap..

believecom.tistory.com

정상적으로 소스를 추가 했다면 아래 화면을 확인할 수 있습니다.

상단에 텍스트가 추가된 ToolBar가 적용되어 있습니다.

파일 탐색기를 사용하면 다양하게 이동할 수 있기 때문에 모든 이벤트 내용을 저장해서 위치를 확인해야 합니다.

모든 내용을 저장하기 위해서 BackStackManager 클래스를 생성하겠습니다.

main 패키지 하단에 BackStackManager.kt를 추가해주세요.

class BackStackManager {
    private var files = mutableListOf<FileModel>()
    var onStackChangeListener: ((List<FileModel>) -> Unit)? = null

    val top: FileModel
        get() = files[files.size - 1]

    fun addToStack(fileModel: FileModel) {
        files.add(fileModel)
        onStackChangeListener?.invoke(files)
    }

    fun popFromStack() {
        if (files.isNotEmpty())
            files.removeAt(files.size - 1)
        onStackChangeListener?.invoke(files)
    }

    fun popFromStackTill(fileModel: FileModel) {
        files = files.subList(0, files.indexOf(fileModel) + 1)
        onStackChangeListener?.invoke(files)
    }
}

BackStackManager 클래스는 3개의 함수로 운영됩니다.

files 변수를 사용해서 파일 이동 경로를 저장합니다.

onStackChangeListener 이벤트를 사용해서 수진 정보를 확인합니다.

top 변수는 최상단 파일 정보를 리턴하게 적용합니다.

이제 BackStackManager을 적용하기 위해서 MainActivity로 이동합니다.

backStackManager 변수를 선언 합니다.

 private fun initViews() {
        setSupportActionBar(toolbar)

        breadcrumbRecyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        mBreadcrumbFileAdapter = BreadcrumbFileAdapter()
        breadcrumbRecyclerView.adapter = mBreadcrumbFileAdapter
        mBreadcrumbFileAdapter.onItemClickListener = {
            supportFragmentManager.popBackStack(it.path, 2);
            backStackManager.popFromStackTill(it)
        }
    }
    private fun initBackStack(){
        backStackManager.onStackChangeListener = {
            updateAdapterData(it)
        }
        backStackManager.addToStack(fileModel = FileModel(Environment.getExternalStorageDirectory().absolutePath, FileType.FOLDER, "/", 0.0))
    }
    
    private fun updateAdapterData(files:List<FileModel>){
        mBreadcrumbFileAdapter.updateData(files)
        if(files.isNotEmpty()){
            breadcrumbRecyclerView.smoothScrollToPosition(files.size - 1)
        }
    }

toolbar 및 backStack를 초기화하기 위한 initViews(), InitBackStack() 함수를 생성합니다.

InitViews() 함수는 처음에 생성한 BreadcrumbFileAdapter을 사용해서 layout View에 연동합니다.

initBackStack() 함수는 BackStackManager가 동작할 경우 업데이트를 실행합니다.

이제 마지막으로 폴더를 선택할 경우 backStackManager를 연동해서 파일 정보를 업데이트합니다.

AddFileFragment(), onBackPressed() 함수에  backStackManager 클래스 메서드를 호출합니다.

이제 파일 탐색기에서 폴더를 이동하면 상단에 폴더 경로를 한눈에 확인할 수 있습니다.

폴더 경로 정보는 매우 단순한 내용처럼 보이지만 파일 이동에 따른 중요한 요소입니다.

오늘은 파일 탐색기 4번째 시간으로 파일 경로를 추가했습니다.

감사합니다.

http://thetechnocafe.com/build-a-file-explorer-in-kotlin-part-4-adding-breadcrumbs/

 

Build a File Explorer in Kotlin – Part 4 – Adding Breadcrumbs - TheTechnoCafe

In this File Explorer tutorial you will add the functionality which enables the user to navigate back to any position in the backstack.

thetechnocafe.com

 

반응형
반응형

Android에서 toolbar를 사용할 경우 추가 요소에 따른 오류가 발생합니다.

오류 내용

"java.lang.IllegalStateException: This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set windowActionBar to false in your theme to use a Toolbar instead"

 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.office.secuex/com.office.secuex.SecuExplorer}: java.lang.IllegalStateException: This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set windowActionBar to false in your theme to use a Toolbar instead.
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3270)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
     Caused by: java.lang.IllegalStateException: This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set windowActionBar to false in your theme to use a Toolbar instead.
        at androidx.appcompat.app.AppCompatDelegateImpl.setSupportActionBar(AppCompatDelegateImpl.java:345)
        at androidx.appcompat.app.AppCompatActivity.setSupportActionBar(AppCompatActivity.java:130)
        at com.office.secuex.SecuExplorer.initViews(SecuExplorer.kt:67)
        at com.office.secuex.SecuExplorer.onCreate(SecuExplorer.kt:40)
        at android.app.Activity.performCreate(Activity.java:7802)
        at android.app.Activity.performCreate(Activity.java:7791)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 
        at android.os.Handler.dispatchMessage(Handler.java:107) 
        at android.os.Looper.loop(Looper.java:214) 
        at android.app.ActivityThread.main(ActivityThread.java:7356) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

상기 오류는 toolbar가 먼저 구성되어 있는 상태에서 추가를 할 경우 발생하는 오류 입니다.

setSupportActionBar(toolbar)

함수를 사용할 경우 많이 발생합니다.

오류 해결 방법

오류를 해결하기 위해서는 res/values/styles.xml 파일에

<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>

상기 내용을 추가하면 오류 없이 컴파일 할 수 있습니다.

감사합니다.

반응형
반응형

Android에서 AppBarLayout을 추가하면 컴파일에는 문제가 없지만 실행 시점 오류가 발생합니다.

오류 내용은 'java.lang.ClassNotFoundException: Didn't find class "com.google.android.material.appbar.AppBarLayout"' 입니다.

at android.app.Activity.performCreate(Activity.java:7802)
        at android.app.Activity.performCreate(Activity.java:7791)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
E/AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "com.google.android.material.appbar.AppBarLayout" on path: DexPathList

실행 시점에 발생하는 오류이기 때문에 패키지 모듈이 없어서 발생하는 문제로 확인됩니다.

build.gradle에 AppBarLayout에 필요한 패키지를 선언하면 해결됩니다.

 

Android ClassNotFoundException 해결 방법
implementation 'com.google.android.material:material:1.0.0'

혹 버전이 다를 경우 메인 클래스에서 오류가 발생할 수 있습니다.

버전 확인 후 패키지를 등록하면 정상적으로 AppBarLayout을 사용할 수 있습니다.

감사합니다.

반응형
반응형

오늘은 코틀린(Kotlin)에서 XML을 사용하는 방법에 대해서 알아보겠습니다.

XML은 다양한 포맷을 지원하기 때문에 JSON 비해 느린 단점이 있지만 

일반적으로 사용하기 매우 편리한 저장 포맷입니다.

android에서는 외부 파일을 확인하기 위해서 일반적인 접근이 불가능합니다.

그래서 미리 만들어둔 XML 파일을 APK에 포함시켜 사용할 수 있는 방법을 적용해야 XML을 쉽게 접근할 수 있습니다.

android에서는 APK에 XML 파일을 포함할 수 있는 에셋(asset) 폴더를 생성해서 접근이 가능합니다.

안드로이드(android) 프로젝트를 생성하면 assets 폴더는 포함되어 있지 않습니다.

폴더 추가를 사용해서 assets 폴더를 생성하고 info.xml을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>
<book>
   <bookinfo id="1111" name="책1번"  des="책1번은 요리책입니다." />
   <bookinfo id="1112" name="책2번"  des="책2번은 요리책입니다." />
   <bookinfo id="1113" name="책3번"  des="책3번은 요리책입니다." />
   <bookinfo id="1114" name="책4번"  des="책4번은 요리책입니다." />
</book>

간단하게 XML을 로드하기 위해서 4개의 로드를 생성합니다.

정상적으로 XML이 만들어지면 브라우저에서 트리 구조 파일을 확인할 수 있습니다.

코틀린(kotlin)에서 XML을 사용하기 위해서는 org.xmlpull.v1.XmlPullParser,

org.xmlpull.v1.XmlPullParserFactory 패키지를 포함해야 합니다.

먼저 InputStream을 사용해서 생성한 폴더 assets에 접근해서 XML 파일을 open 합니다.

XmlPullParserFactory를 사용해서 신규 인터페이스를 생성합니다.

XmlPullParser를 생성해서 factory와 연결 후 xml파일을 로드합니다.

var xmlinfo:InputStream = assets.open("info.xml")

var factory:XmlPullParserFactory = XmlPullParserFactory.newInstance()

var parser:XmlPullParser = factory.newPullParser()

parser.setInput( xmlinfo, null)

전체 XML 파일을 확인하기 위해서 타입을 확인하고 END_DOCUMENT까지 로드를 반복합니다.

설정한 tag는 "bookinfo"이므로 if 문에서 "bookinfo"일 경우만 getAttributeValue를 사용해서 하위 로드에 접근합니다.

getAttributeValue는 배열 형태로 Value 정보를 확인할 수 있습니다.

while( event != XmlPullParser.END_DOCUMENT){

                var tag_name = parser.name

                when(event){
                    XmlPullParser.END_TAG ->{
                        if( tag_name == "bookinfo")
                        {
                            var item:String = parser.getAttributeValue(0) + parser.getAttributeValue(1) + parser.getAttributeValue(2)
                            Toast.makeText(this, item, Toast.LENGTH_SHORT).show()
                            AddTextView(item)
                        }
                    }
                }
                event = parser.next()
            }

parser.next()를 사용해서 다음 로드로 이동하면 됩니다.

코틀린(kotlin)을 사용하면 XML을 쉽게 관리할 수 있어 매우 편리합니다.

다양한 외부 정보를 관리하기 위해서는 XML은 꼭 필요한 포맷이기 때문에 공부하시면 좋습니다.

컴파일하면 XML 로드와 동일하게 리스트를 생성합니다.

XML과 JSON을 많이 비교합니다.

JSON은 상대적으로 XML보다 빠르기 때문에 서버 데이터 전송에 많이 사용됩니다.

XML 장점은 UTF-8만 지원하는 JSON과 다르게 다양한 인코딩 형식을 지원하기 때문에 범용적으로 사용하기 좋습니다.

읽기/쓰기 속도에 큰 문제가 없는 프로그램이라면 XML 사용을 적극 추천드립니다.

감사합니다.

 

 

반응형
반응형

파일 탐색기를 사용하면 파일과 폴더 타입에 따라서 폴더 이동이 가능해야 합니다.

오늘은 코틀린(Kotlin)을 이용한 탐색기 세 번째 파트 파일 탐색기 이벤트 연동에 대해서 알아보겠습니다.

파일 탐색기 이벤트 연동은 파일, 폴더를 구분해서 재 탐색을 해야 합니다.

먼저 FilesListFragment에서 OnItemClickListener 이벤트 변수를 선언합니다.

아래쪽에 interface를 사용해서 onClick, onLogClick 함수를 선언합니다.

private lateinit var eCallback: OnItemClickListener

    interface OnItemClickListener{
        fun onClick(fileModel : FileModel)

        fun onLongClick(fileModel : FileModel)
    }

override onAttach를 선언하고 OnItemClickListener로 캐스팅하여 eCallback에 연결합니다.

onAttach는 실행 시점에 호출되기 때문에 오류를 방지하기 위해서 catch를 사용해서 오류를 정의합니다.

 override fun onAttach(context: Context?) {
        super.onAttach(context)

        try{
            eCallback = context as OnItemClickListener
        } catch( e: Exception){
            throw Exception("${context} FileListFragment onAttach")
        }
    }

FilesListFragment 클래스를 MainActivity에 상속합니다.

클래스명을 선언하면 OnItemClickListener 이벤트를 확인할 수 있습니다.

FilesListFragment 클래스는 interface로 onClick, onLongClick를 선언했기 때문에 상속을 받게 되면 interface를 선언해야 오류가 발생하지 않습니다.

onClick, onLongClick를 override하여 선언합니다.

onClick이벤트가 발생하면 container에 View를 실행하고 파일 정보를 넘겨줄 수 있는 AddFileFragment를 선언합니다.

	override fun onClick(fileModel: FileModel) {
       
    }

    override fun onLongClick(fileModel: FileModel) {
       
    }

    private fun AddFileFragment(fileModel: FileModel){
        val filesListFragment = FilesListFragment.build {
            path = fileModel.path
        }

        val fragmentTransaction = supportFragmentManager.beginTransaction()
        fragmentTransaction.replace(R.id.container, filesListFragment)
        fragmentTransaction.addToBackStack(fileModel.path)
        fragmentTransaction.commit()
    }

onClick 이벤트에 폴더일 경우 파일 경로를 저장하기 위해서 AddFileFragment를 연결합니다.

스택에 더 이상 확인할 내용이 없을 경우 종료할 수 있게 onBackPressed를 override 해줍니다.

override fun onBackPressed(){
        super.onBackPressed()
        if( supportFragmentManager.backStackEntryCount ==0){
            finish()
        }
    }

파일을 선택하면 파일 확장자에 연결된 앱을 실행하기 위해서 이벤트를 연동해야 합니다.

FileUtils.kt파일에 앱을 실행할 수 있는 함수 launchFileIntent를 선언합니다.

fun Context.launchFileIntent(fileModel: FileModel) {
    val intent = Intent(Intent.ACTION_VIEW)
    intent.data = FileProvider.getUriForFile(this, packageName, File(fileModel.path))
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    startActivity(Intent.createChooser(intent, "Select Application"))
}

launchFileintent 함수는 fileModel 경로에 확인된 확장자에 따라서 설치된 앱을 실행합니다.

폴더가 아닐 경우 launchFileIntent 함수를 사용해서 앱 실행을 적용합니다.

override fun onClick(fileModel: FileModel) {
        if( fileModel.fileType == FileType.FOLDER){
            AddFileFragment(fileModel)
        }else{
            launchFileIntent(fileModel)
        }
    }

onClick 함수는 폴더일 경우 AddFileFragment를 재 호출하여 리스트를 생성합니다.

파일 경우 launchFileIntent 함수를 사용해서 앱을 실행하고 뒤로 가기를 클릭하면 다시 폴더 리스트를 호출합니다.

마지막으로 InitViews함수에서 초기화 시점에 mFilesAdapter에 클릭 이벤트를 연동합니다.

mFilesAdapter.onItemClickListener = {
            eCallback.onClick(it)
        }

        mFilesAdapter.onItemLongClickListener = {
            eCallback.onLongClick(it)
        }

onItemClickListener, onItemLongClickListener 함수에 eCallback 이벤트를 연동하면 리스트 클릭 시점에 eCallback가 호출되면서 이벤트가 연동됩니다.

컴파일해서 실행하면 리스트에서 하위 폴더가 있을 경우 리스트가 변경되는 것을 확인할 수 있습니다.

폴더 하단에 아무것도 없을 경우는 "there is nothing here" View를 확인할 수 있습니다.

파일을 클릭하면 연결된 앱 실행 여부를 확인하고 앱을 실행할 수 있습니다.

탐색기를 사용한 이벤트 연동은 어렵게 보이지만, 기본은 간단한 이벤트 연동이기 때문에 조금만 보면 바로 확인할 수 있습니다.

다음 시간에는 파일 경로를 확인할 수 있는 로직을 추가해보겠습니다.

감사합니다.

 

 

http://thetechnocafe.com/build-a-file-explorer-in-kotlin-part-3-navigating-through-file-system/

 

Build a File Explorer in Kotlin – Part 3 – Navigating through File System - TheTechnoCafe

Welcome to the 3rd part of the Kotlin File Explorer series. Good job reading files from a path in the previous tutorial. Right now our application takes...

thetechnocafe.com

 

반응형
반응형

오늘은 파이썬(Python) 파이토치(PyTorch)에서 자연어를

처리하기 위해서 우선적으로 해야 할 토큰 만들기를 알아보겠습니다.

자연어를 처리하기 위해서는 단어를 구분해서

문장을 해석해야 합니다.

 

간단하게 문장과 공백으로도 나눌 수 있지만

정확한 의미를 확인하기 위해서는 문장과 공백만을

구분해서는 사용하기 힘듭니다.

 

NLP 작업에 사용되는 말뭉치(corpus)는 원시 텍스트와

메타데이터로 구성됩니다.

원시 텍스트, 메타데이터를 구분하기 위해서

오픈 소스 NLP 패키지는 대부분 코큰화 기능을 지원합니다.

대표적인 NLP 두가지 제품을 사용해서 토큰화를 확인해보겠습니다.

 

첫 번째 오픈 NLP 패키지 spacy

https://spacy.io/

 

spaCy · Industrial-strength Natural Language Processing in Python

spaCy is a free open-source library for Natural Language Processing in Python. It features NER, POS tagging, dependency parsing, word vectors and more.

spacy.io

spacy를 사용하기 위해서는 먼저 패키지를 설정해야 합니다.

spacy는 패키지 설치 시간이 조금 오래 걸립니다.

import spacy

import를 사용해서 spacy 패키지를 로드합니다.

def spacyex():
    nlp = spacy.load('en_core_web_sm')

    # Process whole documents
    text = ("When Sebastian Thrun started working on self-driving cars at "
            "Google in 2007, few people outside of the company took him "
            "seriously. “I can tell you very senior CEOs of major American "
            "car companies would shake my hand and turn away because I wasn’t "
            "worth talking to,” said Thrun, in an interview with Recode earlier "
            "this week.")
    doc = nlp(text)

    # Analyze syntax
    print("Noun phrases:", [chunk.text for chunk in doc.noun_chunks])
    print("Verbs:", [token.lemma_ for token in doc if token.pos_ == "VERB"])

    # Find named entities, phrases and concepts
    for entity in doc.ents:
        print(entity.text, entity.label_)

spacy.load() 함수를 사용해서 core를 로드합니다.

text에 일반 문장으로 입력합니다.

nlp를 사용해서 토큰화를 진행합니다.

출력 결과 일반 문장을 토큰화 하여 출력합니다.

token.pos_를 사용하면 단어를 분류하여

품사 태깅을 확인할 수 있습니다.

혹 spacy를 사용 중 오류가 발생하면 아래 내용을 참고해주세요.

https://believecom.tistory.com/746

 

python spacy 모델 사용 중 Can't find model 'en_core_web_sm'. It doesn't seem to be a Python package 오류 처리 하기

python 자연어 처리를 공부하면서 spacy모델을 사용한 기본 테스트를 하게 되면 오류가 발생합니다. 오류 내용은 "Can't find model 'en_core_web_sm'. It doesn't seem to be a Python package" 입니다. 모델이..

believecom.tistory.com

 

두 번째 오픈 NLP 패키지 nltk

https://www.nltk.org/

 

Natural Language Toolkit — NLTK 3.6.2 documentation

Natural Language Toolkit NLTK is a leading platform for building Python programs to work with human language data. It provides easy-to-use interfaces to over 50 corpora and lexical resources such as WordNet, along with a suite of text processing libraries

www.nltk.org

nltk 패키지도 import를 사용해서 설치가 가능합니다.

nltk는 패키지 다운로드 후 컴파일을 진행하면

추가 패키지를 다운로드해야 합니다.

nltk.download() 함수를 사용해서 오류 발생 시

패키지를 하나씩 선택하면 됩니다.

def nltkex():
    nltk.download('punkt')
    nltk.download('averaged_perceptron_tagger')
    nltk.download('maxent_ne_chunker')
    nltk.download('words')
    nltk.download('treebank')
    sentence = """At eight o'clock on Thursday morning Arthur didn't feel very good."""
    tokens =nltk.word_tokenize(sentence)
    print(tokens)
    tagged = nltk.pos_tag(tokens)
    print(tagged[0:6])

    eltities = nltk.chunk.ne_chunk(tagged)
    print(eltities)

    t = treebank.parsed_sents('wsj_0001.mrg')[0]
    t.draw()

nltk.word_tokenize() 함수를 사용해서 토큰화를 진행합니다.

nltk.pos_tag()함수는 품사 태깅을 진행 후 

토큰 정보를 출력할 수 있습니다.

간단한 함수 사용으로 토큰화를 진행할 수 있습니다.

청크 나구기를 하기 위해서는 nltk.chunk.ne_chunk() 함수를 사용합니다.

간단한 출력으로 문장의 청크를 구분할 수 있습니다.

파이토치(PyTorch) 자연어 처리에서 토큰, 청크 단위로 문장을

구분하면 정확한 구조를 확인하기 위해서 트리구조가 가장 좋습니다.

nltk 패키지에서는 treebank를 사용해서 트리구조

내용을 확인할 수 있습니다.

자연어 처리에서 문장을 처리하기 위한

토큰화는 가장 기본되는 작업입니다.

두 가지 오픈 패키지를 사용해서 우선적인 자연어 처리

기본을 공부하면 좋겠습니다.

감사합니다.

 

반응형
반응형

python 자연어 처리를 공부하면서 spacy모델을

사용한 기본 테스트를 하게 되면 오류가 발생합니다.

오류 내용은 "Can't find model 'en_core_web_sm'.

It doesn't seem to be a Python package" 입니다.

모델이 'en'이 포함되어 있지 않은 것으로 확인됩니다.

오류를 해결하기 위해서 검색해보니까

추가 설치가 필요하다고 합니다.

"python -m spacy download en"

cmd를 실행해서 en 모델을 추가 설치하면

정상적으로 컴파일됩니다.

커멘드 실행 후 spacy 모델에서 추가 내용을 다운로드합니다.

컴파일하면 정상적으로 token 생성이 가능합니다.

반응형
반응형

파이썬(python)은 다수 스크립트 파일을 생성해서

프로젝트를 구성할 수 있습니다.

각 스크립트는 main을 추가할 수 있으면

함수 및 모든 데이터 정보를 저장할 수 있습니다.

오늘은 파이썬(python) 메인 스크립트에서

다른 스크립트를 호출하는 방법을 알아보겠습니다.

파이썬(python)에서는 스크립트 파일을

3가지 형태로 호출할 수 있습니다.

첫 번째 import 사용

import 함수는 패키지를 포함시키는 함수이지만

스크립트 파일도 포함시킬 수 있습니다.

item.py 신규 파일을 만들어서 2개의 함수를 정의합니다.

import 함수에 선택 파일 이름을 설정합니다.

,py 확장자는 빼주세요.

정상적으로 임포트 되었다면 파일명을 입력하면

스크립트에 저장된 함수를 확인할 수 있습니다.

컴파일하면 main 함수 및 item 스크립트 함수

정보가 출력됩니다.

 

두 번째 exec 사용

두 번째는 exec 함수를 사용해서

스크립트 파일을 직접 접근할 수 있습니다.

직접 함수를 사용하지 않고 메임 함수를 호출하는 구조로

import 함수 사용과는 조금 다릅니다.

새로운 스트립트 파일을 생성 후 main을

포함해서 함수를 작성해주세요.

버전이 낮은 파이썬(python)2 버전은

execfile() 함수를 사용하면 됩니다.

exec(open("item2.py").read())

exex를 사용하면 스크립트를 확인하고

main 함수를 바로 호출합니다.

 

세 번째 subprocess 사용

subprocess 모듈을 사용하면 새 프로세스를

생성하거나 출력을 반환할 수 있습니다.

subprocess를 사용하기 전에 모듈을 import 해주세요

subprocess.call("item2.py", shell=True)

subprocess 모듈을 사용해서 신규 스크립트 파일을

실행 후 main 실행 반환 값을 확인할 수 있습니다.

subprocess, exec 사용은 스크립트를

메인을 확인하는 구조이기 때문에

유틸리티 형태를 사용하고 싶다면

import를 사용해서 직접 접근하는 방식이

가장 좋은 방법입니다.

파이썬(python)을 공부하게 되면 정말 많은

유틸리티를 만날 수 있기 때문에

필요하다면 별도 저장 후 프로그램 

개발에 적극 활용해주세요.

감사합니다.

 

반응형
반응형

코틀린(Kotlin)을 사용 파일 탐색기 만들기는

다양한 컨트롤을 사용해서 개발 가능하지만

조금 더 자유롭게 리스트를 구성할 수 있는

RecyclerView를 사용해서 구현해보겠습니다.

 

RecyclerView는 인스타그램, 유튜브 피드, 전화번호부

등과 같이 동일한 형태의 뷰의 데이터에 따라서 자유롭게

구성할 수 있는 컨트롤입니다.

기존에는 ListView를 많이 사용했지만 커스터마이징이

힘든 단점이 부각되면서 RecyclerView를 많이 사용합니다.

 

먼저 파일 탐색기에서 가장 기본으로 사용하는

파일, 폴더 단위를 관리할 수 있는 Fragment를 구성해보겠습니다.

app 트리에서 filelist 패키지를 생성 후

FilesListFragment 코틀린(Kotlin) Class를 생성합니다.

class FilesListFragment : Fragment() {
   companion object {
        private const val ARG_PATH: String = "com.office.secuex"
        fun build(block:Builder.()-> Unit) = Builder().apply(block).build()
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_files_list, container, false)
    }
}

companion object를 선언해서 블록안에

전역 변수를 지정합니다.

onCreateView 실행 시 infalter을 사용해서 레이아웃을 출력합니다.

inflater에 사용하기 위한 fragment_files_list.xml을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout  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">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/filesRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScroolingViewBehavior" />

    <LinearLayout
        android:id="@+id/emptyFolderLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="@drawable/background_circle"
            android:padding="40dp"
            android:src="@drawable/ic_folder_dark_24dp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_marginTop="16dp"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:textStyle="bold"
            android:text="There is nothing here!"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:text="Empty Folder."/>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout >

RectclerView 속성인 app:layout_hehavior은

최신 버전 형태로 변경해야 합니다.

build.gradle app 항목에 아래 내용을 추가합니다.

implementation("androidx.recyclerview:recyclerview:1.2.1")
implementation("androidx.recyclerview:recyclerview-selection:1.1.0")

패키지를 추가하면 RecyClerView를 사용할 수 있습니다.

아이템 정보를 연결할 수 있는 빌더 클래스를

FileListFragment에 추가합니다.

    class Builder{
        var path : String = ""
        fun build(): FilesListFragment{
            val fragment = FilesListFragment()
            val args = Bundle()
            args.putString(ARG_PATH, path)
            fragment.arguments = args;
            return fragment
        }
    }

모든 변수에 연결할 수 있는 Builder 클래스는

입력받은 FilesListFragment 정보를 연결합니다.

이제 생성한 FilesListFragment를 MainActivity에

연결해야 합니다.

 

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

        if( savedInstanceState == null)
        {
            val filesListFragment = FilesListFragment.build {
                path = Environment.getExternalStorageDirectory().absolutePath
            }

            supportFragmentManager.beginTransaction()
                .add( R.id.container, filesListFragment)
                .addToBackStack( Environment.getExternalStorageDirectory().absolutePath)
                .commit()
        }

savedInstanceState가 없을 경우

filesListFragment를 생성해서 백그라운드로 실행시킵니다.

MainActivity 레이아웃에 FrameLayout를 생성합니다.

 <FrameLayout
        app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScroolingViewBehavior"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/container">
    </FrameLayout>

FrameLayout 속성 중 app:layout_behavior을

최신 버전 형태로 변경합니다.

FrameLayout를 사용해서 Activity가 실행되면

RecyclerView를 실행할 수 있는 기본 구조를 모두 확인했습니다.

이번에는 RecyclerView에 들어갈 파일, 폴더 정보를

정리해보겠습니다.

common 폴더를 생성하고 FileType.kt 파일을 생성합니다.

파일 타입, 파일 모델을 확인할 수 있는 두 개의

클래스를 정의합니다.

package com.office.secuex.common

import java.io.File

enum class FileType {
    FILE,
    FOLDER;

    companion object{
        fun getFileType(file: File) = when(file.isDirectory){
            true -> FOLDER
            false -> FILE
        }
    }

}

data class FileModel(
    val path : String,
    val fileType : FileType,
    val name : String,
    val sizeInMB: Double,
    val extension: String ="",
    val subFiles: Int = 0
)

FileModel은 경로, 타입, 이름

사이즈, 확장자로 구분됩니다.

파일 경로, 용량, 파일 모델을 구성하기 위한

함수를 utils 패키지를 생성해서 정의합니다.

package com.office.secuex.utils

import com.office.secuex.common.FileModel
import com.office.secuex.common.FileType
import java.io.File

fun getFilesFromPath( path : String, showHiddenFiles : Boolean = false, onlyFolders:Boolean = false)
        : List<File>{
    val file = File(path)
    return file.listFiles()
        .filter { showHiddenFiles || !it.name.startsWith(".")}
        .filter { !onlyFolders || it.isDirectory}
        .toList()
}

fun getFileModelsFromFiles(files: List<File>) : List<FileModel>{
    return files.map {
        FileModel(it.path, FileType.getFileType(it), it.name,
        convertFileSizeToMB(it.length()), it.extension, it.listFiles()?.size?:0)
    }
}

fun convertFileSizeToMB(sizeInBytes: Long) : Double{
    return ( sizeInBytes.toDouble()) / (1024 * 1024)
}

getFilesFromPath() 함수를 사용해서

파일 리스트를 확인할 수 있습니다.

FilesListFragment에서 사용하는

RectclerView에 연결하기 위한

item_recycler_file.xml 레이아웃을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="1dp"
    android:background="@color/colorPrimary"
    android:foreground="?selectableItemBackground"
    android:padding="16dp">

    <TextView
        android:id="@+id/nameTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Pictuers" />

    <TextView
        android:id="@+id/folderTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="(Folder)"
        android:textSize="12sp"
        android:visibility="gone"
        app:layout_constraintLeft_toLeftOf="@id/nameTextView"
        app:layout_constraintRight_toRightOf="@id/nameTextView"
        app:layout_constraintTop_toBottomOf="@id/nameTextView" />

    <TextView
        android:id="@+id/totalSizeTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="4 MB"
        android:textSize="12sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/folderTextView" />

</androidx.constraintlayout.widget.ConstraintLayout >

하단에 있는 참고 사이트와는 버전이 다르기 때문에

최신 버전은 ConstraintLayout를 사용해야 최신 버전에서

컴파일이 정상적으로 진행됩니다.

이제 RecyclerView에 정보를 연결할 수 있는

FilesRecyclerAdapter를 만들어 보겠습니다.

filelist 패키지 아래에 FilesRecyclerAdapter 클래스를 생성합니다.

package com.office.secuex.filelist

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.office.secuex.R
import com.office.secuex.common.FileModel
import com.office.secuex.common.FileType
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_recycler_file.view.*

class FilesRecyclerAdapter : RecyclerView.Adapter<FilesRecyclerAdapter.ViewHolder>() {

    var onItemClickListener: ((FileModel) -> Unit)? = null
    var onItemLongClickListener: ((FileModel) -> Unit)? = null

    var filesList = listOf<FileModel>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_recycler_file, parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount() = filesList.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bindView(position)

    fun updateData(filesList: List<FileModel>) {
        this.filesList = filesList
        notifyDataSetChanged()
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {
        init {
            itemView.setOnClickListener(this)
            itemView.setOnLongClickListener(this)
        }

        override fun onClick(v: View?) {
            onItemClickListener?.invoke(filesList[adapterPosition])
        }

        override fun onLongClick(v: View?): Boolean {
            onItemLongClickListener?.invoke(filesList[adapterPosition])
            return true
        }

        fun bindView(position: Int) {
            val fileModel = filesList[position]
            itemView.nameTextView.text = fileModel.name

            if (fileModel.fileType == FileType.FOLDER) {
                itemView.folderTextView.visibility = View.VISIBLE
                itemView.totalSizeTextView.visibility = View.GONE
                itemView.folderTextView.text = "(${fileModel.subFiles} files)"
            } else {
                itemView.folderTextView.visibility = View.GONE
                itemView.totalSizeTextView.visibility = View.VISIBLE
                itemView.totalSizeTextView.text = "${String.format("%.2f", fileModel.sizeInMB)} mb"
            }
        }
    }
}

RectclerView.adapter을 사용해서

파일 및 폴더 정보를 접근할 수 있습니다.

마지막으로 MainActivity에 연결된

FilesListFragment에 Adapter를 연결합니다.

package com.office.secuex.filelist

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.office.secuex.R
import com.office.secuex.common.FileModel
import com.office.secuex.common.FileType
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_recycler_file.view.*

class FilesRecyclerAdapter : RecyclerView.Adapter<FilesRecyclerAdapter.ViewHolder>() {

    var onItemClickListener: ((FileModel) -> Unit)? = null
    var onItemLongClickListener: ((FileModel) -> Unit)? = null

    var filesList = listOf<FileModel>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_recycler_file, parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount() = filesList.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bindView(position)

    fun updateData(filesList: List<FileModel>) {
        this.filesList = filesList
        notifyDataSetChanged()
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {
        init {
            itemView.setOnClickListener(this)
            itemView.setOnLongClickListener(this)
        }

        override fun onClick(v: View?) {
            onItemClickListener?.invoke(filesList[adapterPosition])
        }

        override fun onLongClick(v: View?): Boolean {
            onItemLongClickListener?.invoke(filesList[adapterPosition])
            return true
        }

        fun bindView(position: Int) {
            val fileModel = filesList[position]
            itemView.nameTextView.text = fileModel.name

            if (fileModel.fileType == FileType.FOLDER) {
                itemView.folderTextView.visibility = View.VISIBLE
                itemView.totalSizeTextView.visibility = View.GONE
                itemView.folderTextView.text = "(${fileModel.subFiles} files)"
            } else {
                itemView.folderTextView.visibility = View.GONE
                itemView.totalSizeTextView.visibility = View.VISIBLE
                itemView.totalSizeTextView.text = "${String.format("%.2f", fileModel.sizeInMB)} mb"
            }
        }
    }
}

컴파일을 진행하면 정상적으로 컴파일이 되면서

RecyclerView를 확인할 수 있어야 합니다.

그런데 Error가 발생하면서 컴파일이 안됩니다.

Error 내용은 파일에 접근을 할 수 없는 내용입니다.

스토리지 접근에 필요한 정보를 Manifest.xml에 추가했습니다.

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

권한을 추가해도 오류가 동일하게 발생합니다.

확인 결과 Application에 추가 권한 필요했습니다.

Application에 추가적으로 아래 속성을 추가합니다.

 android:requestLegacyExternalStorage="true"

자 그럼 다시 한번 컴파일을 해보겠습니다.

기본 스토리지 정보가 List에 출력되는 것을 확인할 수 있습니다.

코틀린(Kotlin)을 사용해서 리스트를 출력하기

위해서는 많은 코딩이 필요하지만

기본 내용을 이해하면 대부분 같은 패턴을 구성되기 때문에

한 번은 꼭 직접 코딩을 하면서 확인해주십시오.

저도 참고 내용을 확인하면서 진행했지만

버전 문제 따른 다양한 오류를 경험했습니다.

오늘은 코틀린(Kotlin)을 사용해서 파일 탐색기

기본 UI를 만들었습니다.

다음 시간에는 RecyclerView에서 이벤트

연동을 해보겠습니다.

감사합니다.

http://thetechnocafe.com/build-a-file-explorer-in-kotlin-part-2-reading-files-from-paths/

 

Build a File Explorer in Kotlin – Part 2 – Reading files from paths - TheTechnoCafe

In this tutorial you will read the list of files/folders on a given path and display them in a RecyclerView along with their size and meta data such as...

thetechnocafe.com

 

반응형
반응형

Android 프로그램을 개발하면서

최신 버전에서 발생하는 behavior 화면 스크롤하는

기능 오류를 발견했습니다.

해결법을 찾아 보니 최신 버전은

다른 코드를 사용해야 합니다.

<android.support.v7.widget.RecyclerView
android:id="@+id/filesRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />

@string/appbar_scrolling_view_behavior

속성을 사용하면

컴파일 시 Android resource linking failed

오류가 발생합니다.

   <android.support.v7.widget.RecyclerView
        android:id="@+id/filesRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScroolingViewBehavior" />

behavior 속성을

com.google.android.material.appbar.AppBarLayout$ScroolingViewBehavior

변경하면 컴파일되면서 오류가 해결됩니다.

감사합니다.

반응형

+ Recent posts