Разработка приложения-галереи под Android на Kotlin

Разработка /
Разработка: Разработка приложения-галереи под Android на Kotlin

В этом уроке мы разработаем полноценное приложение — галерею изображений на Kotlin. Также мы рассмотрим следующие темы:

  • Реализация паттерна Singleton на Kotlin.
  • Передача экземпляра класса из одной activity в другую с помощью Parcelable на Kotlin
  • Пример реализации Shared preferences.
  • Пример Recyclerview на Kotlin.
  • Настройка RecyclerView Adapter на Kotlin.
  • NavigationView и настройка DrawerLayout на Kotlin.
  • Загрузка и вывод изображений с внутреннего накопителя Android на Kotlin.
  • Использование библиотеки для загрузки изображений Glide.
  • Работа с разрешениями в режиме выполнения в Android.

Если вы ещё новичок и только планируете освоить Kotlin, рекомендую вначале ознакомиться со следующими материалами:

  1. Идиомы Kotlin (англ.)
  2. Классы, объекты, модификаторы и интерфейсы в Kotlin (англ.)


Таким образом, разработав это приложение, вы неплохо освоите Kotlin.

Итак, приступим.

Создадим новый проект в Android Studio и включим галочку “Add support for Kotlin” при его создании.

Добавим необходимые разрешения в манифест.


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


Создадим Data Class “Albums” и реализуем интерфейс Parcelable.

Вот наш класс модели, хранящий информацию о папках изображений (например, Whatsapp Images, Camera), пути до последних изображений в папке, общем количестве изображений в каждой папке.

И так как мы планируем передать эту информацию с окна запуска приложения в MainActivity с помощью Intent, мы должны реализовать интерфейс Parcelable в классе “Albums”:


data class Albums(var folderNames: String, var imagePath: String, var imgCount: Int, var isVideo: Boolean) : Parcelable {
    constructor(parcel: Parcel) : this(
            parcel.readString(),
            parcel.readString(),
            parcel.readInt(),
            parcel.readByte() != 0.toByte()) {
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(folderNames)
        parcel.writeString(imagePath)
        parcel.writeInt(imgCount)
        parcel.writeByte(if (isVideo) 1 else 0)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Albums> {
        override fun createFromParcel(parcel: Parcel): Albums {
            return Albums(parcel)
        }

        override fun newArray(size: Int): Array<Albums?> {
            return arrayOfNulls(size)
        }
    }
}


Добавим Splash Activity

В методе onCreate нашей Splash Activity нам сперва необходимо получить доступ к внешнему накопителю. Для этого мы должны запросить у пользователя разрешение на доступ к изображениям на устройстве.

После того, как пользователь даст разрешение read_external_storage, мы сможем загрузить изображения и передать имя изображения, его путь и количество изображений в папках в нашу Main Activity для вывода изображений в Recyclerview.


// SplashActivity.kt

internal var SPLASH_TIME_OUT = 800

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_splash)

    Handler().postDelayed(
            {
                 // проверка того, что пользователь дал разрешение на доступ к накопителю.
                 // в противном случае запросим такое разрешение.
                if (!checkSelfPermission()) {
                    requestPermission()
                } else {
                   // если разрешение получено, загрузим изображения.
                   // исходный код этого метода будет описан ниже.
                    loadAllImages()
                }
            }, SPLASH_TIME_OUT.toLong())
}

Добавим код, перехватывающий выдачу разрешения.


private fun requestPermission() {
    ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 6036)
}

private fun checkSelfPermission(): Boolean {

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        return false
    } else
        return true
}

Когда пользователь даст разрешение или откажет в нём, выполнится следующий код:


override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    when (requestCode) {
        6036 -> {
            if (grantResults.size > 0) {
                var permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED
                if (permissionGranted) {

    // Теперь мы готовы к чтению внешнего хранилища и имеющихся там изображений.

                    loadAllImages()
                } else {
                    Toast.makeText(this, "Permission Denied! Cannot load images.", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}

Если разрешение получено, мы передадим загруженные изображения в MainActivity.kt

Передаём экземпляр класса данных (класса модели) в intent-е как Parcelable.

Эта функция вызовет другой метод getAllShownImagesPath(context), которая вернёт List типа Albums.

Передадим полученный список Albums в MainActivity с помощью Intent.

MainActivity выведет список всех папок с изображениями и видео (типа Camera, Instagram, Whatsapp Images) в RecyclerView.


fun loadAllImages() {
    var imagesList = getAllShownImagesPath(this)
    var intent = Intent(this, MainActivity::class.java)
    intent.putParcelableArrayListExtra("image_url_data", imagesList)
    startActivity(intent)
    finish()
}

В этом куске кода у нас есть экземпляр Data Class и нам необходимо передать эти данные в другую activity, поэтому мы передаём сериализованные данные.

Загрузка изображений из внутреннего хранилища.

Здесь мы рассмотрим чтение всех папок с изображениями и видео с помощью класса MediaStore. И вернём ArrayList.


private fun getAllShownImagesPath(activity: Activity): ArrayList<Albums> {

    val uri: Uri
    val cursor: Cursor
    var cursorBucket: Cursor
    val column_index_data: Int
    val column_index_folder_name: Int
    val listOfAllImages = ArrayList<String>()
    var absolutePathOfImage: String? = null
    var albumsList = ArrayList<Albums>()
    var album: Albums? = null


    val BUCKET_GROUP_BY = "1) GROUP BY 1,(2"
    val BUCKET_ORDER_BY = "MAX(datetaken) DESC"

    uri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI

    val projection = arrayOf(MediaStore.Images.ImageColumns.BUCKET_ID,
            MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
            MediaStore.Images.ImageColumns.DATE_TAKEN,
            MediaStore.Images.ImageColumns.DATA)

    cursor = activity.contentResolver.query(uri, projection, BUCKET_GROUP_BY, null, BUCKET_ORDER_BY)

    if (cursor != null) {
        column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
        column_index_folder_name = cursor
                .getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)
        while (cursor.moveToNext()) {
            absolutePathOfImage = cursor.getString(column_index_data)
            Log.d("title_apps", "bucket name:" + cursor.getString(column_index_data))

            val selectionArgs = arrayOf("%" + cursor.getString(column_index_folder_name) + "%")
            val selection = MediaStore.Images.Media.DATA + " like ? "
            val projectionOnlyBucket = arrayOf(MediaStore.MediaColumns.DATA, MediaStore.Images.Media.BUCKET_DISPLAY_NAME)

            cursorBucket = activity.contentResolver.query(uri, projectionOnlyBucket, selection, selectionArgs, null)
            Log.d("title_apps", "bucket size:" + cursorBucket.count)

            if (absolutePathOfImage != "" && absolutePathOfImage != null) {
                listOfAllImages.add(absolutePathOfImage)
                albumsList.add(Albums(cursor.getString(column_index_folder_name), absolutePathOfImage, cursorBucket.count, false))
            }
        }
    }
    return getListOfVideoFolders(albumsList)
}

// Эта функция отвечает за чтение всех видео из всех папок.
private fun getListOfVideoFolders(albumsList: ArrayList<Albums>): ArrayList<Albums> {

    var cursor: Cursor
    var cursorBucket: Cursor
    var uri: Uri
    val BUCKET_GROUP_BY = "1) GROUP BY 1,(2"
    val BUCKET_ORDER_BY = "MAX(datetaken) DESC"
    val column_index_album_name: Int
    val column_index_album_video: Int

    uri = android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI

    val projection1 = arrayOf(MediaStore.Video.VideoColumns.BUCKET_ID,
            MediaStore.Video.VideoColumns.BUCKET_DISPLAY_NAME,
            MediaStore.Video.VideoColumns.DATE_TAKEN,
            MediaStore.Video.VideoColumns.DATA)

    cursor = this.contentResolver.query(uri, projection1, BUCKET_GROUP_BY, null, BUCKET_ORDER_BY)

    if (cursor != null) {
        column_index_album_name = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_DISPLAY_NAME)
        column_index_album_video = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
        while (cursor.moveToNext()) {
            Log.d("title_apps", "bucket video:" + cursor.getString(column_index_album_name))
            Log.d("title_apps", "bucket video:" + cursor.getString(column_index_album_video))
            val selectionArgs = arrayOf("%" + cursor.getString(column_index_album_name) + "%")

            val selection = MediaStore.Video.Media.DATA + " like ? "
            val projectionOnlyBucket = arrayOf(MediaStore.MediaColumns.DATA, MediaStore.Video.Media.BUCKET_DISPLAY_NAME)

            cursorBucket = this.contentResolver.query(uri, projectionOnlyBucket, selection, selectionArgs, null)
            Log.d("title_apps", "bucket size:" + cursorBucket.count)

            albumsList.add(Albums(cursor.getString(column_index_album_name), cursor.getString(column_index_album_video), cursorBucket.count, true))
        }
    }
    return albumsList
}


Итак, что мы уже сделали

  1. Запрос разрешения на чтение изображений в режиме выполнения.
  2. С помощью MediaStore и Cursor реализовали загрузку путей до изображений из внутренней памяти.
  3. Научились передавать экземпляр сериализованного класса данных (Serialized Data Class) из одной Activity в другую на Kotlin.
  4. Поняли как использовать Data Class в Kotlin (Albums.kt).

Теперь создадим другую activity и назовём её MainActivity.kt. В ней мы сделаем следующее:

  1. Мы получим объект Albums, отправленный из Splash Activity.
  2. Напишем код слушателя нажатия на элемент RecyclerView.
  3. Создадим файл макета (разметки) и адаптер RecyclerView для вывода всех папок.

Разметка для MainActivity


//файл activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:fitsSystemWindows="true">

    <android.support.v7.widget.Toolbar
        android:id="@+id/my_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp"
        app:titleTextColor="@color/colorIcons" />
    
    <android.support.v4.widget.DrawerLayout
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/my_toolbar"
        android:fitsSystemWindows="true">

        <!-- Ваш контент -->
        <include layout="@layout/include_main_content"></include>

        <android.support.design.widget.NavigationView
            android:id="@+id/navigation"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:onClick="toast"
            app:menu="@menu/my_navigation_menu"
            app:theme="@style/NavigationDrawerStyle" />
    </android.support.v4.widget.DrawerLayout>
</RelativeLayout>


//include_main_content.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:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <android.support.v7.widget.RecyclerView
        android:id="@+id/rvAlbums"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab_camera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:layout_margin="16dp"
        android:src="@drawable/ic_photo_camera_white_24dp"
        app:layout_anchor="@id/rvAlbums"
        app:layout_anchorGravity="bottom|right|end"
        app:layout_behavior="com.title_apps.canalpic.util.ScrollAwareFABBehavior" />

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


//MainActivity.kt

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

    savedState = savedInstanceState

    if (savedState != null)
        folder_name = savedInstanceState!!.getString("folder_name")

    setSupportActionBar(my_toolbar)
    // Включим кнопку "Вверх" (Up button)
    supportActionBar!!.setDisplayHomeAsUpEnabled(true)
    supportActionBar!!.setHomeAsUpIndicator(resources.getDrawable(R.drawable.ic_menu_white_24dp))

    setupNavigationView()

    var extra = intent.extras;
    if (extra != null) {
        var extraData = extra.get("image_url_data") as ArrayList<Albums>
        select_fragment(extraData)
    }

    drawer_layout_listener()
    supportActionBar!!.setTitle("Folders")
}


// в этом методе мы посылаем имя папки, по которой кликнул пользователь. К примеру, если пользователь
// нажал на папку downloads из общего списка папок нашего приложения, мы передадим это 
// имя папки следующей activity, которая загрузит и выведет на экран все изображения из этой папки.

override fun onItemClick(position: String, isVideo: Boolean) {

    var bundle = Bundle()
    bundle.putString("folder_name", position)
        var intent = Intent(this, AlbumActivity::class.java)
        intent.putExtra("folder_name", position)
        startActivity(intent)
}


Инициализируем Glide и настраиваем RecyclerView для вывода изображений.


private var folder_name: String = ""

public fun select_fragment(imagesList: ArrayList<Albums>) {

    val options = RequestOptions()
            .diskCacheStrategy(DiskCacheStrategy.RESOURCE).override(160, 160).skipMemoryCache(true).error(R.drawable.ic_image_unavailable)
    val glide = Glide.with(this)

    val builder = glide.asBitmap()
    rvAlbums?.layoutManager = GridLayoutManager(this, 2)

    rvAlbums?.setHasFixedSize(true)

    // AlbumFoldersAdapter.kt это класс адаптера RecyclerView.
    rvAlbums?.adapter = AlbumFoldersAdapter(imagesList, this, options, builder, glide, this)


    rvAlbums?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
        }

        override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            when (newState) {
                RecyclerView.SCROLL_STATE_IDLE -> glide.resumeRequests()
                AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL, AbsListView.OnScrollListener.SCROLL_STATE_FLING -> glide.pauseRequests()
            }
        }
    }
    )

    fab_camera?.setOnClickListener(object : View.OnClickListener {
        override fun onClick(p0: View?) {
            launchCamera()
        }
    }
    )
}

Слушатели для NavigationView и drawerLayout


// слушатель клика для drawer layout.
private fun drawer_layout_listener() {

    drawer_layout.addDrawerListener(object : DrawerLayout.DrawerListener {
        override fun onDrawerStateChanged(newState: Int) {
        }

        override fun onDrawerSlide(drawerView: View?, slideOffset: Float) {
        }

        override fun onDrawerClosed(drawerView: View?) {
            supportActionBar!!.setHomeAsUpIndicator(resources.getDrawable(R.drawable.ic_menu_white_24dp))
        }

        override fun onDrawerOpened(drawerView: View?) {
            supportActionBar!!.setHomeAsUpIndicator(resources.getDrawable(R.drawable.ic_keyboard_backspace_white_24dp))
        }
    }
    )
}

// слушатель клика для элементов Navigation.
private fun setupNavigationView() {

    navigation.setNavigationItemSelectedListener(object : NavigationView.OnNavigationItemSelectedListener {
        override fun onNavigationItemSelected(item: MenuItem): Boolean {
            drawer_layout.closeDrawer(Gravity.START)
            when (item.itemId) {
                R.id.nav_all_folders -> {
                                 }
                R.id.nav_hidden_folders -> {
                                 }
            }
            return false
        }
    })
}


Добавим код в AlbumsFolderAdapter.kt для RecyclerView

Создаём файлы разметки для адаптера RecyclerView из MainActivity


// list_layout.xml
// layout file for RecyclerView adapter.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="@dimen/five_dp"
        android:elevation="3dp"
        card_view:cardCornerRadius="@dimen/zero_dp">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:background="?attr/selectableItemBackgroundBorderless">

            <ImageView
                android:id="@+id/thumbnail"
                android:layout_width="match_parent"
                android:layout_height="@dimen/album_cover_height"
                android:scaleType="centerCrop" />

            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/thumbnail"
                android:paddingLeft="@dimen/ten_dp"
                android:paddingRight="@dimen/ten_dp"
                android:paddingTop="@dimen/ten_dp"
                android:text="Camera"
                android:textColor="@color/colorPrimaryText"
                android:textSize="@dimen/fifteen_dp" />

            <RelativeLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/title">

                <TextView
                    android:id="@+id/photoCount"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:paddingBottom="@dimen/five_dp"
                    android:paddingLeft="@dimen/ten_dp"
                    android:paddingRight="@dimen/six_dp"
                    android:textColor="@color/colorSecondaryText"
                    android:textSize="@dimen/twelve_dp" />
            </RelativeLayout>
        </RelativeLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

Создадим ещё класс, назовём файл с ним AlbumFoldersAdapter.kt — это будет адаптер для RecyclerView.


class AlbumFoldersAdapter(val albumList: ArrayList<Albums>, val context: Context, val options: RequestOptions, val glide: RequestBuilder<Bitmap>, val glideMain: RequestManager, val inOnItemClick: IOnItemClick) : RecyclerView.Adapter<AlbumFoldersAdapter.ViewHolder>() {

 override fun onViewRecycled(holder: ViewHolder?) {
 if (holder != null) {
 //glideMain.clear(holder.itemView.thumbnail)
 // glide.clear(holder.itemView.thumbnail)
 //Glide.get(context).clearMemory()
 // holder?.itemView?.thumbnail?.setImageBitmap(null)
 }// Glide.clear(holder?.itemView?.thumbnail)
 super.onViewRecycled(holder)

}

override fun onViewDetachedFromWindow(holder: ViewHolder) {
 if (holder != null) {
 // glideMain.clear(holder.itemView.thumbnail)
 //Glide.get(context).clearMemory()
 // holder?.itemView?.thumbnail?.setImageBitmap(null)
 
}

super.onViewDetachedFromWindow(holder)
 }

override fun getItemCount(): Int {
 return albumList.size
 }

override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
 holder?.bindItems(albumList.get(position), glide, options, inOnItemClick, albumList.get(position).isVideo)

holder?.itemView?.title?.setText(albumList.get(position).folderNames)
 if (albumList.get(position).isVideo)
 holder?.itemView?.photoCount?.setText("" + albumList.get(position).imgCount + " videos")
 else
 holder?.itemView?.photoCount?.setText("" + albumList.get(position).imgCount + " photos")
 }

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

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

fun bindItems(albumList: Albums, glide: RequestBuilder<Bitmap>, options: RequestOptions, inOnItemClick: IOnItemClick, isVideo: Boolean) {
 glide.load(albumList.imagePath).apply { options }.thumbnail(0.4f)
 .into(itemView.thumbnail)

itemView.setOnClickListener(object : View.OnClickListener {
 override fun onClick(p0: View?) {
 inOnItemClick.onItemClick(albumList.folderNames, isVideo)
     }
  })
}}}


Добавим Interface для обработки клика в RecyclerView


interface IOnItemClick {
fun onItemClick(position: String, isVideo: Boolean)
}

Итак, мы реализовали вывод всех папок с изображениями и самих изображений выбранной папки в recyclerView.

Также мы создали код для RecyclerView Adapter и использовали библиотеку Glide для загрузки изображений в него.

Создадим новую Activity AlbumsActivity.kt, ответственную за вывод изображений выбранной папки в RecyclerView.

Создадим файл разметки для AlbumsActivity.kt


// activity_album.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:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/my_album_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp"
        app:titleTextColor="@color/colorIcons" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rvAlbumSelected"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?attr/actionBarSize"
        android:clipToPadding="false" />

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


AlbumsActivity.kt


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

    setSupportActionBar(my_album_toolbar)
    // Включим кнопку "Вверх"
    supportActionBar!!.setDisplayHomeAsUpEnabled(true)

    val folder_name = intent.getStringExtra("folder_name")
    supportActionBar!!.setTitle("" + folder_name)
    val isVideo = intent.getBooleanExtra("isVideo", false)
    init_ui_views(folder_name, isVideo)

}



var adapter: SingleAlbumAdapter? = null

private fun init_ui_views(folderName: String?, isVideo: Boolean?) {

    val options = RequestOptions()
            .diskCacheStrategy(DiskCacheStrategy.RESOURCE).override(160, 160).skipMemoryCache(true).error(R.drawable.ic_image_unavailable)
    val glide = Glide.with(this)
    val builder = glide.asBitmap()

        rvAlbumSelected.layoutManager = GridLayoutManager(this, 2)
    rvAlbumSelected?.setHasFixedSize(true)
    adapter = SingleAlbumAdapter(getAllShownImagesPath(this, folderName, isVideo), this, options, builder, glide, this)
    rvAlbumSelected?.adapter = adapter

    rvAlbumSelected?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
        }

        override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            when (newState) {
                RecyclerView.SCROLL_STATE_IDLE -> glide.resumeRequests()
                AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL, AbsListView.OnScrollListener.SCROLL_STATE_FLING -> glide.pauseRequests()
            }
        }
    }
    )
}

// Читаем все пути до изображений в выбранной папке.

private fun getAllShownImagesPath(activity: Activity, folderName: String?, isVideo: Boolean?): MutableList<String> {

    val uri: Uri
    val cursorBucket: Cursor
    val column_index_data: Int
    val listOfAllImages = ArrayList<String>()
    var absolutePathOfImage: String? = null

    val selectionArgs = arrayOf("%" + folderName + "%")

    uri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    val selection = MediaStore.Images.Media.DATA + " like ? "

    val projectionOnlyBucket = arrayOf(MediaStore.MediaColumns.DATA, MediaStore.Images.Media.BUCKET_DISPLAY_NAME)

    cursorBucket = activity.contentResolver.query(uri, projectionOnlyBucket, selection, selectionArgs, null)

    column_index_data = cursorBucket.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)

    while (cursorBucket.moveToNext()) {
        absolutePathOfImage = cursorBucket.getString(column_index_data)
        if (absolutePathOfImage != "" && absolutePathOfImage != null)
            listOfAllImages.add(absolutePathOfImage)
    }
    return listOfAllImages.asReversed()
}


Слушатель клика для Album Activity


override fun onItemClick(position: String, isVideo: Boolean) {
    val intent = Intent(this, PhotoActivity::class.java)
    intent.putExtra("folder_name", position)
    startActivity(intent)
}


RecyclerView adapter для AlbumActivity.kt


class SingleAlbumAdapter(val albumList: MutableList<String>, val context: Context, val options: RequestOptions, val glide: RequestBuilder<Bitmap>, val glideMain: RequestManager, val inOnItemClick: IOnItemClick) : RecyclerView.Adapter<SingleAlbumAdapter.ViewHolder>() {


    override fun onViewRecycled(holder: ViewHolder?) {
        if (holder != null) {
        }
        super.onViewRecycled(holder)
    }

    override fun onViewDetachedFromWindow(holder: ViewHolder) {
        if (holder != null) {

        }
        super.onViewDetachedFromWindow(holder)
    }

    override fun getItemCount(): Int {
        return albumList.size
    }

    override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
        holder?.bindItems(albumList.get(position), glide, options, inOnItemClick)
    }

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

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bindItems(albumList: String, glide: RequestBuilder<Bitmap>, options: RequestOptions, inOnItemClick: IOnItemClick) {

            glide.load(albumList).apply { options }.thumbnail(0.4f)
                    .into(itemView.thumbnail)

            itemView.setOnClickListener(object : View.OnClickListener {
                override fun onClick(p0: View?) {
                    inOnItemClick.onItemClick(albumList, false)
                }
            })
        }
    }
}

Файл разметки для Single Album Adapter


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="@dimen/five_dp"
        android:elevation="3dp"
        card_view:cardCornerRadius="@dimen/zero_dp">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:background="?attr/selectableItemBackgroundBorderless">

            <ImageView
                android:id="@+id/thumbnail"
                android:layout_width="@dimen/album_cover_height"
                android:layout_height="@dimen/album_cover_height"
                android:scaleType="centerCrop" />
        </RelativeLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>


Теперь добавим Detail Activity, выводящую единственное изображение


// SingleActivity.kt

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

    setSupportActionBar(toolbar)
    // Enable the Up button
    supportActionBar!!.setDisplayHomeAsUpEnabled(true)
    supportActionBar!!.setDisplayShowTitleEnabled(false)

    val folder_name = intent.getStringExtra("folder_name")
    Glide.with(this).load(folder_name).into(imageFullScreenView)

    Handler().postDelayed(Runnable
    {
        if (supportActionBar != null)
            appbar.animate().translationY(-appbar.bottom.toFloat()).setInterpolator(AccelerateInterpolator()).start()
        isAppBarShown = false
    }, 1500)

}



// activity_photo.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
    android:background="@android:color/black"
    tools:context="com.title_apps.canalpic.screens.detail.PhotoActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#93000000">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:elevation="4dp"
            app:titleTextColor="@color/colorIcons" />
    </android.support.design.widget.AppBarLayout>


    <ImageView
        android:id="@+id/imageFullScreenView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

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


Резюмируя

Сегодня мы рассмотрели полный пример реализации приложения — галереи изображений под Android, а также освоили синтаксис Kotlin, классы данных, библиотеку Glide, RecyclerView и его адаптер, NavigationView, Drawer Layout и реализацию слушателей для них на Kotlin.

По материалам «How to Develop Android Image Gallery App in Kotlin Tutorial with Complete Source Code»
0 комментариев
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.