Many Android developers faced the problem of implementing animations and transitions when opening new fragments. We are offered to use either adding fragments to a container, layering them on each other, or replace (replacing one fragment with another). Replays have four types of animations:
.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()
With replays, the problem lies in the fact that a) - the previous fragment is destroyed, b) - there is no possibility to specify an action to close the fragment with a gesture (for example, as implemented in Google Inbox).
Adding fragments to the stack (add) allows you to use animations only to the opened fragment, the back will be fixed.
And all this, of course, is accompanied by poor rendering and broken frames.
As a result, even such large applications as VKontakte or Instagram do not use fragment animations in their applications at all.
A year and a half ago, Telegram was introduced to Telegram x (a test version of its client). They solved this problem like this:
Here the animation of the front and rear fragments is implemented, as well as the ability to close the fragments with a gesture.
I managed to do something similar and I would like to share my method of opening fragments:
So, create the class 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) } } }
Now we have our Navigator, which we use as a container for all the fragments in our Activiti:
<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"/>
Set the background black. This is necessary to simulate the shadow on the closed fragment. more will be clearer.
Now we need an adapter in which we will place the 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`) } }
Immediately create a Transformer for our Navigator:
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 } } } } }
Now - the most interesting! We register the necessary actions for the discovery of fragments in our Activiti:
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() } } }
That's all. Now on any fragment we call the method from Activiti:
(activity as MainActivity).addFragment(ConversationFragment())
When swiped to the right, it will be removed from the stack itself with the help of our OnPageChangeListener.
This method is not ideal, it is possible that some pitfalls will open in further development, but I have not yet found any problems with usability, perhaps experienced developers will correct me or prompt something. See how it all works on a real example here .
Source: https://habr.com/ru/post/453166/
All Articles