Yazdığınız uygulamanın kullanıcı sayısının artmasını sağlayan en önemli konulardan biri, kullanıcı deneyimi yüksek ve dikkat çekici tasarıma sahip olmasıdır.
Android uygulama da UI öğeleri arasındaki geçişleri farklı animasyon görünümleri ile sağlayan çeşitli Material motion özellikleri bulunmaktadır. Bu özelliklerden biri olan Shared Axis hakkındaki örnek projeyi “Material Motion Part 1: Shared Axis” adlı makalemden ulaşabilirsiniz.
Bu makale serisinde, Android uygulamada iki farklı arayüz elementinin birbirleri arasında animasyonlu bir şekilde geçişini sağlayan Material motion Container Transform özelliğini örnekleyeceğim.
Container Transform
Bir arayüz elementinin başka bir arayüz elementine animasyonlu bir şekilde geçişini sağlayan özelliktir. Böylelikle kullanıcının mobil tasarım öğeleriyle etkileşime girmesini sağlamış oluruz. Bu durum projenin uzun vadede kullana bilirliğini artırır.
Makalemizde anlatacağım örnek uygulamanın görüntüsü;
Projemin kodlarına github linkinden hızlıca ulaşabilirsiniz.
Bu örneği uygulayabilmek için işlemleri adım adım yapalım.
1-Gerekli Kütüphanelerin Yüklenmesi
Projemin app dizinin altındaki build.gradle dosyasını açıyoruz. Dependencies kod bloklarının arasına aşağıdaki kodları yerleştirerek navigation ve material kütüphanelerini yüklüyoruz.
implementation('androidx.navigation:navigation-fragment-ktx:2.3.1')
implementation('androidx.navigation:navigation-ui-ktx:2.3.1')
implementation('com.google.android.material:material:1.3.0-alpha03')
2- Kotlin ve tasarım kodları
İlk önce Note sınıfından bahsetmek istiyorum. Tüm not başlıklarını, içeriklerini ve not kutularının renklerini atadığım ve bu değerleri bir listOf array yapısında barındırdığım sınıfdır.
import androidx.annotation.ColorRes
data class Note(
val id: Int,
val title: String,
val body: String,
@ColorRes val colorRes: Int
)
val note1 = Note(
id = 1,
title = "ToDo 1",
body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus scelerisque placerat nisl, nec semper felis ullamcorper vel. Nullam egestas ante nec tortor egestas mattis. Duis ut diam nec nibh sodales commodo at at diam. Nunc tempor eu lectus ut feugiat. Etiam eget ullamcorper est, at scelerisque lectus. Aliquam erat volutpat. Maecenas est urna, vestibulum non eros non, dignissim feugiat mi. Cras sit amet ex hendrerit, accumsan dolor in, bibendum erat. Maecenas ullamcorper ut risus eget congue. Vestibulum aliquam ipsum ut turpis efficitur, vel malesuada neque aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean egestas justo id rutrum blandit. Vestibulum id orci libero. Aliquam pharetra sed mauris ac vehicula.",
colorRes = R.color.blue300
)
val note2 = Note(
id = 2,
title = "ToDo 2",
body = "Fusce hendrerit enim in eros congue, sed pharetra libero tempus. Integer accumsan euismod nibh non vestibulum. Curabitur finibus imperdiet nunc vel ornare. Ut maximus fringilla sapien in viverra. Aenean a nulla feugiat, hendrerit risus et, congue erat. Ut venenatis lorem sit amet volutpat sollicitudin. Donec ac lorem auctor sem mattis faucibus non ac ante. Phasellus id sem non ante bibendum porta non in tellus. Etiam pellentesque porta luctus.",
colorRes = R.color.amber300
)
val note3 = Note(
id = 3,
title = "ToDo 3",
body = "Praesent interdum dictum magna quis pretium. Suspendisse at cursus ante, id rutrum nunc. Nullam at lacinia nibh, nec gravida lectus. Quisque maximus vulputate leo, et sollicitudin turpis luctus pellentesque. Nullam vehicula sagittis magna, consectetur congue neque ullamcorper vel. Praesent sed vulputate nunc. Ut ligula lorem, lobortis tristique interdum at, mollis a dolor. Ut sed fringilla urna, id suscipit justo. Praesent ut tortor pharetra, laoreet metus non, iaculis mi. Duis vel lacus fermentum, porta neque id, ultricies arcu. Nunc interdum, est ac sodales tristique, elit urna feugiat dui, quis venenatis ante lorem ullamcorper mi. Nulla id condimentum lorem. Nunc ac scelerisque felis. Nam semper, mi et ultrices rutrum, neque odio molestie tortor, ac iaculis ante arcu eu elit. Phasellus imperdiet tortor quis aliquam ornare.",
colorRes = R.color.green300
)
val notes = listOf(note1, note2, note3)
MainActivity sınıfımızda ise, notların grid şeklinde gösterilmesi sağlayan ve not kutucuklarına tıklandığı anda yapılan geçiş animasyonunu tetiklemesini yaptık. Detaylı açıklamalar kodlar arasında bulunmaktadır.
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.MenuItem
import androidx.core.app.ActivityOptionsCompat
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.card.MaterialCardView
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setRecyclerView()
}
private fun setRecyclerView() {
//Note data class da tanımlanan başlık ve içerikler NotesAdapter sınıfına gönderiliyor
val adapter = NotesAdapter(notes)
//Not kutularına tıklanma eventi
adapter.noteClickListener = object : NotesAdapter.NoteClickListener {
override fun onNoteClick(id: Int, noteCard: MaterialCardView) {
val intent = Intent(this@MainActivity, NoteDetailActivity::class.java)
//Not kutusuna tıkladığı anda yapılan geçiş animasyonunu tetiklediğimiz bölüm
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
this@MainActivity, noteCard, id.toString()
)
//İlgili içeriğin id'si NoteDetailActivity sınıfına gönderiliyor
intent.putExtra("noteId", id)
startActivity(intent, options.toBundle())
}
}
//Notları grid şeklinde gösterilmesi sağlayan bölüm
recycler_view.layoutManager = GridLayoutManager(this, 2)
recycler_view.adapter = adapter
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> {
true
}
}
}
override fun onBackPressed() {
finish()
}
}
Notların grid şeklinde gösterilmesi için activity_main xml dosyasında RecyclerView arayüz elementi kullanıldı.
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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:fitsSystemWindows="true"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
style="@style/Widget.MaterialComponents.AppBarLayout.Surface"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
style="?attr/textAppearanceHeadline6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/material_on_surface_emphasis_medium"
android:text="My notes" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
NotesAdapter sınıfının işlevi, MainActivity sınıfında kullanılan RecyclerView içinde tüm not item’larını oluşturmaktır. Bu item kutucuklarında da Note sınıfından gelen notlarla ilgili içerikleri item_note xml dosyasına aktarmasıyla tasarımda gösterilmesidir.
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.smality.materialcontainertransform.databinding.ItemNoteBinding
import com.google.android.material.card.MaterialCardView
class NotesAdapter(private val items: List<Note>) : RecyclerView.Adapter<NotesAdapter.ViewHolder>() {
lateinit var noteClickListener: NoteClickListener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ViewHolder(
ItemNoteBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun getItemCount() = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position])
inner class ViewHolder(val binding: ItemNoteBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(note: Note) {
binding.note = note
binding.noteCard.transitionName = note.id.toString()
binding.noteCard.setOnClickListener {
noteClickListener.onNoteClick(note.id, binding.noteCard)
}
}
}
interface NoteClickListener {
fun onNoteClick(id: Int, noteCard: MaterialCardView)
}
}
item_note xml dosyasında MaterialCardView arayüz elementini notları kutucuk şeklinde göstermek için kullandık. MaterialCardView içinde de MaterialTextView arayüz elementine not başlık ve içeriklerini atadık.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="note"
type="com.smality.materialcontainertransform.Note" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/note_card"
android:layout_gravity="center"
android:clickable="true"
android:focusable="true"
android:layout_margin="8dp"
app:cardBackgroundColor="@{context.resources.getColor(note.colorRes)}"
android:layout_width="match_parent"
android:layout_height="200dp">
<LinearLayout
android:padding="16dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textview.MaterialTextView
style="?attr/textAppearanceBody1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{note.title}" />
<com.google.android.material.textview.MaterialTextView
style="?attr/textAppearanceCaption"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ellipsize="end"
android:maxLines="10"
android:layout_marginTop="8dp"
android:text="@{note.body}" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>
</layout>
Son olarak, NoteDetailActivity sınıfını kullandık. Bu sınıf, not kutularına tıklandığında notların detaylandırının gösterildiği sayfadır. Diğer bir yandan bu sınıfta, RecyclerView nesnesinden MaterialTextView nesnesine geciş anının süresinin belirlendiği kod sayfasıdır. Detaylı açıklamalar kodlar arasında bulunmaktadır.
import android.os.Bundle
import android.view.MenuItem
import android.view.Window
import androidx.appcompat.app.AppCompatActivity
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.google.android.material.transition.platform.MaterialContainerTransform
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
import com.smality.materialcontainertransform.databinding.NoteDetailActivityBinding
class NoteDetailActivity : AppCompatActivity() {
private lateinit var binding: NoteDetailActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
binding = NoteDetailActivityBinding.inflate(layoutInflater)
//MainActivity sınıfından gelen noteId değerini aldık
val noteId = intent.getIntExtra("noteId", 0)
//Id bilgisine göre Note data class daki not içeriğini alıp, note nesnesine atadık
val note = notes.find { it.id == noteId }
binding.note = note
//
binding.coordinator.transitionName = noteId.toString()
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
//buildContainerTransform metodunda oluturulan özelliklerin geciş esnasında uygulanması için
//yapılan atama
window.sharedElementEnterTransition = buildContainerTransform()
window.sharedElementReturnTransition = buildContainerTransform()
setContentView(binding.root)
super.onCreate(savedInstanceState)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowTitleEnabled(false)
}
//RecyclerView nesnesinden MaterialTextView nesnesine geciş anının süresini belirkeyen metod
private fun buildContainerTransform() =
MaterialContainerTransform().apply {
addTarget(binding.coordinator)
duration = 300
//geçiş anındaki animasyon hızını ayarlayan metod
interpolator = FastOutSlowInInterpolator()
fadeMode = MaterialContainerTransform.FADE_MODE_IN
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> {
true
}
}
}
}
note_detail_activity xml dosyasında ise, MaterialTextView arayüz elementi kullanılarak seçilen notun detaylarının gösterildi.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="note"
type="com.smality.materialcontainertransform.Note" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@{context.resources.getColor(note.colorRes)}">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.textview.MaterialTextView
style="?attr/textAppearanceHeadline6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@{note.title}"
android:textColor="@color/material_on_surface_emphasis_medium" />
</com.google.android.material.appbar.MaterialToolbar>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:layout_marginTop="?actionBarSize">
<com.google.android.material.textview.MaterialTextView
style="?attr/textAppearanceBody2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ellipsize="end"
android:maxLines="10"
android:layout_marginTop="8dp"
android:text="@{note.body}" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
Dilerseniz projemin kodlarına github linkinden hızlıca ulaşabilirsiniz.






Henüz yorum yapılmamış, sesinizi aşağıya ekleyin!