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!