/**
 * 리사이클러뷰 아답터를 상속받는 아답터
 * 데이터 초기화.
 */
class CustomAdapter(private val dataSet: Array) :
        RecyclerView.Adapter() {

    /**
     * Provide a reference to the type of views that you are using (custom ViewHolder)
     */
    class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
        val textView: TextView

        /**홀더 초기화.*/
        init {
            // Define click listener for the ViewHolder's View.
            v.setOnClickListener { Log.d(TAG, "Element $adapterPosition clicked.") }
            textView = v.findViewById(R.id.textView)
        }
    }

    // Create new views (invoked by the layout manager)
    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        // Create a new view.
        val v = LayoutInflater.from(viewGroup.context)
                .inflate(R.layout.text_row_item, viewGroup, false)

        return ViewHolder(v)
    }

    // Replace the contents of a view (invoked by the layout manager)
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        Log.d(TAG, "Element $position set.")

        // Get element from your dataset at this position and replace the contents of the view
        // with that element
        viewHolder.textView.text = dataSet[position]
    }

    // Return the size of your dataset (invoked by the layout manager)
    override fun getItemCount() = dataSet.size

    /**로그켓 사용하는 태그 초기화.*/
    companion object {
        private val TAG = "CustomAdapter"
    }
}



코디네이터레이아웃 사용해서 스크롤시 

툴바 및 리사이클러뷰 스크롤시 최상단 및 최하단 터치 감지가 불량일 때가 있다.

최상단 혹은 최하단 스크롤 후 리스트 항목을 클릭이 되지 않아 두번 클릭해야하는데 


안드로이드 sdk 28에서는 정상동작 한다.

이전 버전에서 AppBarLayout.Behavior 에 버그가 있는듯.



android coordinatorlayout recyclerview scroll click

이렇게 검색하니 스택오버플로우에 올라온 글

Click not working on RecyclerView in CoordinatorLayout when scrolling



코틀린 코드만 입력


게시글 중 해당 링크가 있고 수정된 코드 java와 kotlin이 있음.


https://gist.github.com/erikhuizinga/edf408167b46eb5b1568424563ca4e59


kotlin

//////////////////////////////////////////////////////////////////////////////////////////

/**

 * Workaround AppBarLayout.Behavior for https://issuetracker.google.com/66996774

 *

 *

 * See https://gist.github.com/chrisbanes/8391b5adb9ee42180893300850ed02f2 for

 * example usage.

 *

 * Kotlinised by Erik Huizinga (github: @erikhuizinga).

 */

class FixAppBarLayoutBehavior(context: Context?, attrs: AttributeSet?) :

AppBarLayout.Behavior(context, attrs) {


override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout,

                            target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int,

                            dyUnconsumed: Int, type: Int) {

super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,

dyUnconsumed, type)

stopNestedScrollIfNeeded(dyUnconsumed, child, target, type)

}


override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout,

                               target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {

super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)

stopNestedScrollIfNeeded(dy, child, target, type)

}


private fun stopNestedScrollIfNeeded(dy: Int, child: AppBarLayout, target: View, type: Int) {

if (type == ViewCompat.TYPE_NON_TOUCH) {

val currOffset = topAndBottomOffset

if (dy < 0 && currOffset == 0 || dy > 0 && currOffset == -child.totalScrollRange) {

ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH)

}

}

}

}

//////////////////////////////////////////////////////////////////////////////////////////


코드 사용법 

AppBarLayout abl = findViewById(R.id.app_bar);

((CoordinatorLayout.LayoutParams) abl.getLayoutParams()).setBehavior(new FixAppBarLayoutBehavior());



xml 사용법

<android.support.design.widget.CoordinatorLayout 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">


    <android.support.design.widget.AppBarLayout

            android:id="@+id/app_bar"

            android:layout_height="..."

            android:layout_width="..."

            app:layout_behavior="your.package.FixAppBarLayoutBehavior">


    </android.support.design.widget.AppBarLayout>


    <!-- Content -->


</android.support.design.widget.CoordinatorLayout>






코틀린 리사이클러뷰 예제



매인액티비티 생성.

MainActivity.kt

//////////////////////////////////////////////////////////////////////////////////////////


class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)


    }

}

//////////////////////////////////////////////////////////////////////////////////////////



Build.gradle(Module:app)에 com.android.support:recyclerview-v7 모듈 추가

//////////////////////////////////////////////////////////////////////////////////////////

dependencies {

    implementation fileTree(include: ['*.jar'], dir: 'libs')

    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

    implementation 'com.android.support:appcompat-v7:28.0.0'

    implementation 'com.android.support.constraint:constraint-layout:1.1.3'

    testImplementation 'junit:junit:4.12'

    androidTestImplementation 'com.android.support.test:runner:1.0.2'

    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

//리사이클러뷰 모듈 추가.

    implementation 'com.android.support:recyclerview-v7:28.0.0'

}

//////////////////////////////////////////////////////////////////////////////////////////


리사이클러뷰에 사용할 데이터 생성.


간단한 데이터 출력 하기위한 클래스 생성.

People.Kt

//////////////////////////////////////////////////////////////////////////////////////////

/**

 * 데이터 클래스 생성 및 초기화

 * 바로 생성자를 넣어서 만들어지게 구현.

 * 코틀린에서는 data class 를 지원 하며

 * java에서 처럼 toString 처리 하지 않아도 됨.

 */

data class People(val name: String, val age: String, val address: String)

//////////////////////////////////////////////////////////////////////////////////////////





리사이클러뷰에 보여줄 row layout 생성.


간단히 name, age, address 보여줌.


listview_row.xml

//////////////////////////////////////////////////////////////////////////////////////////

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.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="wrap_content">



    <android.support.constraint.ConstraintLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:layout_marginLeft="10dp"

        android:layout_marginTop="10dp"

        android:layout_marginRight="10dp"

        app:layout_constraintTop_toTopOf="parent">



        <TextView

            android:id="@+id/name"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:text="name"

            app:layout_constraintTop_toTopOf="parent" />


        <TextView

            android:id="@+id/age"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:text="age"

            app:layout_constraintTop_toBottomOf="@+id/name" />


        <TextView

            android:id="@+id/address"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:text="address"

            app:layout_constraintTop_toBottomOf="@+id/age" />



    </android.support.constraint.ConstraintLayout>

</android.support.constraint.ConstraintLayout>

//////////////////////////////////////////////////////////////////////////////////////////



뷰홀더 클래스 생성. RecyclerView.ViewHolder 상속

TestHolder_01.kt

//////////////////////////////////////////////////////////////////////////////////////////

/**생성자 지정 , 상위클래스 view 넘겨줌.  super(itemView);*/

class TestHolder_01(itemView: View) : RecyclerView.ViewHolder(itemView) {

    // 뷰 홀더를 상속 받고나면 생성자에서 상위 홀더에 view 를 전달.

    

    

    val name: TextView

    val age: TextView

    val address: TextView

    

    

    /**

     * 코틀린사용한 초기화

     * 초기화 블럭.

     */

    init {

        this.name = itemView.findViewById(R.id.name)

        this.age = itemView.findViewById(R.id.age)

        this.address = itemView.findViewById(R.id.address)

    }


    

    /**팩토리 함수 */

    companion object {

        fun newInstance(viewGroup: ViewGroup): TestHolder_01 {

            val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.listview_row, viewGroup, false)

            return TestHolder_01(view)

        }

    }


    fun onBindView(position: Int, list: ArrayList<People>) {

        // 데이터를 화면에 그리기.

        name.text = list[position].name

        age.text = list[position].age

        address.text = list[position].address

    }

}

//////////////////////////////////////////////////////////////////////////////////////////





리사이클러뷰 아답터 생성.

TestAdapter_01.kt

//////////////////////////////////////////////////////////////////////////////////////////

/**생성자에서 리스트를 받아옴.*/

class TestAdapter_01(val list: ArrayList<People>) : RecyclerView.Adapter<TestHolder_01>() {



    override fun onCreateViewHolder(p0: ViewGroup, p1: Int): TestHolder_01 {

        //팩토리함수를 이용한 뷰홀더 생성.

        return TestHolder_01.newInstance(p0)

    }


    override fun getItemCount(): Int {

        return list.size

    }


    override fun onBindViewHolder(p0: TestHolder_01, p1: Int) {

        // 홀더에 정의된 함수로 뷰 그리기

        p0.onBindView(p1, list)

    }

}

//////////////////////////////////////////////////////////////////////////////////////////


activity_main.xml

//////////////////////////////////////////////////////////////////////////////////////////

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.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="match_parent"

    tools:context=".MainActivity">


    <android.support.v7.widget.RecyclerView

        android:id="@+id/recyclerView"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:orientation="vertical"

        app:layoutManager="android.support.v7.widget.LinearLayoutManager">


    </android.support.v7.widget.RecyclerView>


</android.support.constraint.ConstraintLayout>

//////////////////////////////////////////////////////////////////////////////////////////



MainActivity.kt

//////////////////////////////////////////////////////////////////////////////////////////

class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

//UI그리기

        setupUI()

    }


//data 생성

    fun setData(): ArrayList<People> {


        val list = arrayListOf<People>()

        for (i in 0 until 30) {

            list.add(People("$i name", "$i age", "$i address"))

        }

        list.forEach {

            Log.e("log", "$it")

        }

        return list

    }


    fun setupUI() {

    //아답터 등록 후 끝.

        recyclerView.adapter = TestAdapter_01(setData())

        

    }

}


//////////////////////////////////////////////////////////////////////////////////////////



안드로이드 추상 클래스 이용한 퍼미션 권한 설정. 2-2

에서 만든 java 소스를 코틀린으로 변환 수정



BaseActivity.kt 

//////////////////////////////////////////////////////////////////////////////////////////

abstract class BaseActivity : AppCompatActivity() {


    abstract val setLayout: Int


    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)


        setContentView(setLayout)

    }



    private val PERMISSION_REQUEST: Int = 1000



    /**인터페이스 등록.*/

    interface PermissionCheckListener {

        fun permissionCheckFinish()

    }


    /**클래스 내 사용할 퍼미션 체크 리스너 (setter 구현 방법이 특이함.)

     * 온 크레딧 바로 아래 생성 되어야 한다

     * */

    lateinit var permissionCheckListener: PermissionCheckListener


    /**

     * 스트링 배열로 퍼미션 체크.

     */

    fun permissionCheck(strings: Array<String>): Boolean {

        // 변수 사용시 리턴 타입 지정.

        // var check = false


        // 변수 사용안하고 바로 리턴시켜 버림.


        //안드로이드 마시멜로 이후 퍼미션 체크.

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

            for (i in 0 until strings.size) {

                if (ContextCompat.checkSelfPermission(this, strings[i]) == PackageManager.PERMISSION_DENIED) {

                    ActivityCompat.requestPermissions(this, strings, PERMISSION_REQUEST)

                    // check = true

                    // break

                    return true

                }

            }

        }

        //return check

        return false

    }



    /**

     * 거부를 했을때 처리

     * 다시보지 않기 체크 후 거부를 하면 설정에서 권한을 허용하도록 요청.

     * 허용하지 않을 경우 앱 사용 을 못함..

     * 처리를 잘못하면 무한 반복할 경우가 생김.

     */

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {


        super.onRequestPermissionsResult(requestCode, permissions, grantResults)


        if (requestCode == PERMISSION_REQUEST) {

            //안드로이드 마시멜로 이후 퍼미션 체크.

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

                for (i in grantResults.indices) {

                    if (grantResults[i] == 0) {

                        if (grantResults.size == i + 1) {

                            permissionCheckListener?.permissionCheckFinish()

                        }

                    } else {


                        // 거부한 이력이 있으면 true를 반환한다.

                        if (shouldShowRequestPermissionRationale(permissions[i])) {

                            ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST)

                            

                        } else {


                           AlertDialog.Builder(this)

                                .setTitle("다시보지않기 클릭.")

                                .setMessage(permissions[i] + " 권한이 거부되었습니다 설정에서 직접 권한을 허용 해주세요.")

                                .setNeutralButton("설정") { dialog, which ->


                                    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)

                                    intent.data = Uri.parse("package:$packageName")

                                    startActivity(intent)

                                    Toast.makeText(applicationContext, "권한 설정 후 다시 실행 해주세요.", Toast.LENGTH_SHORT).show()

                                    finish()

                                }

                                .setPositiveButton("확인") { dialog, which ->

                                    Toast.makeText(applicationContext, "권한 설정을 하셔야 앱을 사용할 수 있습니다.", Toast.LENGTH_SHORT)

                                        .show()

                                    finish()

                                }

                                .setCancelable(false)

                                .create().show()

                        }// shouldShowRequestPermissionRationale /else

                    } // 권한 거절

                } // for end

            }//Build.VERSION.SDK_INT  end

        }//requestCode  end

    }


}

//////////////////////////////////////////////////////////////////////////////////////////




MainActivity에서 사용 방법.


Manifest에 

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

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

퍼미션 등록 확인.


MainActivity.kt

//////////////////////////////////////////////////////////////////////////////////////////

class MainActivity : BaseActivity() {


    override val setLayout = R.layout.activity_main


    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)


        permissionCheckListener = object : PermissionCheckListener {

            override fun permissionCheckFinish() {

                Log.e("log", "start Activity")

            }

        }


        if (permissionCheck(arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE))) {

            return

        }

        permissionCheckListener?.permissionCheckFinish()

    }

}



//////////////////////////////////////////////////////////////////////////////////////////


+ Recent posts