Transformando o ViewPager em um gerenciador de fragmentos com animações no estilo iOS

Muitos desenvolvedores para Android enfrentaram o problema de implementar animações e transições ao abrir novos fragmentos. Somos convidados a adicionar fragmentos ao contêiner, colocá-los em camadas uns sobre os outros ou reproduzir (substituindo um fragmento por outro). A reprodução tem quatro tipos de animações:


Live it tudo se parece com isso:
.beginTransaction() .setCustomAnimations( R.anim.enter_from_left, //   2 R.anim.exit_to_right, //   1 R.anim.enter_from_right, //   1 R.anim.exit_to_left) //   2 .replace(R.id.container, myFragment) .commit() 

imagem


O problema com replays é que: a) o fragmento anterior é destruído; b) não há como definir uma ação para fechar o fragmento com um gesto (por exemplo, conforme implementado na Caixa de entrada do Google).


Adicionar fragmentos à pilha (adicionar) permite que você use animações apenas para que o fragmento seja aberto; a parte traseira ficará imóvel.


E tudo isso, é claro, é acompanhado por uma renderização ruim e quadros quebrados.


Como resultado, mesmo aplicativos grandes como o VKontakte ou o Instagram não usam animações de fragmento em seus aplicativos.


Há um ano e meio, o Telegram introduziu o Telegram x (uma versão de teste de seu cliente). Eles resolveram esse problema assim:


imagem


Aqui, a animação dos fragmentos dianteiro e traseiro é implementada, bem como a capacidade de fechar fragmentos com um gesto.


Consegui fazer algo semelhante e gostaria de compartilhar meu método de abertura de fragmentos:


imagem


Portanto, crie a classe NavigatorViewPager:


 class NavigatorViewPager : ViewPager { init { init() } constructor(context: Context) : super(context) constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) override fun canScrollHorizontally(direction: Int): Boolean { return false } //  private fun init() { // PageTransformer       setPageTransformer(false, NavigatorPageTransformer()) //   overScrollMode = View.OVER_SCROLL_NEVER //       , //    setDurationScroll(300) } //      fun setDurationScroll(millis: Int) { try { val viewpager = ViewPager::class.java val scroller = viewpager.getDeclaredField("mScroller") scroller.isAccessible = true scroller.set(this, OwnScroller(context, millis)) } catch (e: Exception) { e.printStackTrace() } } //      DecelerateInterpolator() inner class OwnScroller(context: Context, durationScroll: Int) : Scroller(context, DecelerateInterpolator(1.5f)) { private var durationScrollMillis = 1 init { this.durationScrollMillis = durationScroll } override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) { super.startScroll(startX, startY, dx, dy, durationScrollMillis) } } } 

Agora temos o nosso Navegador, que usamos como um contêiner para todos os fragmentos em nossa Atividade:


 <info.yamm.project2.navigator.NavigatorViewPager xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/navigator_view_pager" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/black" android:fitsSystemWindows="false" tools:context=".activities.MainActivity"/> 

Plano de fundo conjunto preto. Isso é necessário para simular a sombra no fragmento fechado. além disso, será mais claro.


Agora precisamos de um adaptador no qual colocaremos os fragmentos:


 class NavigatorAdapter(val fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) { // ArrayList      private val listOfFragments: ArrayList<BaseFragment> = ArrayList() //  fun addFragment(fragment: BaseFragment) { listOfFragments.add(fragment) notifyDataSetChanged() } //   fun removeLastFragment() { listOfFragments.removeAt(listOfFragments.size - 1) notifyDataSetChanged() } //     fun getFragmentsCount(): Int { return listOfFragments.size } override fun getItemPosition(`object`: Any): Int { val index = listOfFragments.indexOf(`object`) //        return if (index == -1) PagerAdapter.POSITION_NONE else index } override fun getItem(position: Int): Fragment? { return listOfFragments[position] } override fun getCount(): Int { return listOfFragments.size } override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { super.destroyItem(container, position, `object`) } } 

Crie imediatamente um Transformer para o nosso Navegador:


 class NavigatorPageTransformer : ViewPager.PageTransformer { override fun transformPage(view: View, position: Float) { // PageTransformer       view.apply { val pageWidth = width when { //        position <= -1 -> { //   INVISIBLE       //    visibility = View.INVISIBLE } // ,          position > 0 && position <= 1 -> { alpha = 1f visibility = View.VISIBLE translationX = 0f } //         // (    ,     NavigatorViewPager?) position <= 0 -> { alpha = 1.0F - Math.abs(position) / 2 translationX = -pageWidth * position / 1.3F visibility = View.VISIBLE } //    ,     else -> { visibility = View.INVISIBLE } } } } } 

Agora - o mais interessante! Nós prescrevemos as ações necessárias para abrir fragmentos em nossa Atividade:


 class MainActivity : BaseActivity() { private lateinit var navigatorAdapter: NavigatorAdapter private lateinit var navigatorViewPager: NavigatorViewPager private lateinit var mainFragment: MainFragment override fun onCreate(savedInstanceState: Bundle?) { setTheme(info.yamm.project2.R.style.AppTheme) //   navigatorViewPager = findViewById<NavigatorViewPager>(info.yamm.project2.R.id.navigator_view_pager) //   (    BottomNavigationView   ) mainFragment = MainFragment() //  navigatorAdapter = NavigatorAdapter(supportFragmentManager) //       addFragment(mainFragment) //     navigatorViewPager.adapter = navigatorAdapter //      // :   FragmentStatePagerAdapter,   //     . // FragmentPagerAdapter   ,         // ,    . // ,     INVISIBLE  PageTransformer //     ,        //   .    , //       navigatorViewPager.offscreenPageLimit = 30 var canRemoveFragment: Boolean = false //         var sumPositionAndPositionOffset = 0.0f //   navigatorViewPager.addOnPageChangeListener(object : OnPageChangeListener { //     ,     override fun onPageScrollStateChanged(state: Int) { if (state == 0 && canRemoveFragment) { while ((navigatorAdapter.getFragmentsCount() - 1) > navigatorViewPager.currentItem) { navigatorAdapter.removeLastFragment() } } } //       //        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { canRemoveFragment = position + positionOffset < sumPositionAndPositionOffset sumPositionAndPositionOffset = position + positionOffset } override fun onPageSelected(position: Int) { } }) } //             fun addFragment(fragment: BaseFragment) { navigatorAdapter.addFragment(fragment) navigatorViewPager.currentItem = navigatorViewPager.currentItem + 1 } //    "" override fun onBackPressed() { if (navigatorAdapter.getFragmentsCount() > 1) { navigatorViewPager.setCurrentItem(navigatorViewPager.currentItem - 1, true) } else { finish() } } } 

Isso, de fato, é tudo. Agora, em qualquer fragmento, chamamos o método da Activiti:


 (activity as MainActivity).addFragment(ConversationFragment()) 

E ao deslizar para a direita, ele próprio será removido da pilha com a ajuda de nosso ouvinte OnPageChangeListener.


Esse método não é ideal, talvez algumas armadilhas sejam abertas em desenvolvimento adicional, mas ainda não encontrei nenhum problema de usabilidade, talvez desenvolvedores experientes me corrijam ou me digam algo. Você pode ver como tudo funciona em um exemplo real aqui .

Source: https://habr.com/ru/post/pt453166/


All Articles