Transformer ViewPager en gestionnaire de fragments avec des animations de style iOS

De nombreux développeurs pour Android ont été confrontés au problème de la mise en œuvre d'animations et de transitions lors de l'ouverture de nouveaux fragments. Nous sommes invités à utiliser soit en ajoutant des fragments au conteneur, en les superposant, soit en rejouant (en remplaçant un fragment par un autre). Replay propose quatre types d'animations:


Live it all ressemble à ceci:
.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() 

image


Le problème avec les rediffusions est que a) le fragment précédent est détruit, b) il n'y a aucun moyen de définir une action pour fermer le fragment avec un geste (par exemple, comme implémenté dans Google Inbox).


L'ajout de fragments à la pile (add) vous permet d'utiliser des animations uniquement pour le fragment à ouvrir, celui du dos sera immobile.


Et tout cela, bien sûr, s'accompagne d'un mauvais rendu et de cadres cassés.


Par conséquent, même les grandes applications comme VKontakte ou Instagram n'utilisent pas du tout d'animations de fragments dans leurs applications.


Il y a un an et demi, Telegram a présenté Telegram x (une version test de son client). Ils ont résolu ce problème comme ceci:


image


Ici, l'animation des fragments avant et arrière est implémentée, ainsi que la possibilité de fermer les fragments avec un geste.


J'ai réussi à faire quelque chose de similaire et je voudrais partager ma méthode d'ouverture des fragments:


image


Alors, créez la 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) } } } 

Nous avons maintenant notre navigateur, que nous utilisons comme conteneur pour tous les fragments de notre activité:


 <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"/> 

Fond noir. Cela est nécessaire pour simuler l'ombre sur le fragment fermé. de plus, ce sera plus clair.


Maintenant, nous avons besoin d'un adaptateur dans lequel nous placerons les fragments:


 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`) } } 

Créez immédiatement un transformateur pour notre navigateur:


 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 } } } } } 

Maintenant - le plus intéressant! Nous prescrivons les actions nécessaires à l'ouverture de fragments dans notre Activité:


 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() } } } 

En fait, c'est tout. Maintenant, sur n'importe quel fragment, nous appelons la méthode d'Activiti:


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

Et lorsque vous glissez vers la droite, il sera lui-même supprimé de la pile à l'aide de notre écouteur OnPageChangeListener.


Cette méthode n'est pas idéale, peut-être que certains pièges s'ouvriront au cours du développement, mais je n'ai pas encore trouvé de problème d'utilisation, peut-être que des développeurs expérimentés me corrigeront ou me diront quelque chose. Vous pouvez voir comment tout cela fonctionne sur un exemple réel ici .

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


All Articles