androidandroid-tablayoutmaterial-components-androidandroid-customtabs

Custom TabLayout like in Samsung Music App


The default TabLayout with mode set to scrollable gives the following: Default tabLayout from M3 project created in Android studio

The position of the selected tab indicator is not fixed. If the first tab is selected, tab indicator is at the left most side of the screen.

But below is the view from the Samsung Music app: Samsung music tab layout with different selections

The selected tab is always at the center of the screen and the first/last items are not bound to the start/end of the screen.

Also, they have cool animation: enter image description here

  1. The concern is about keeping the selected tab at the center of the screen.
  2. The documentation for animation for tab change is also not available anywhere. The scale down animation as and when the selected tab moves away and the scale up animation when a non-selected tab is selected.

Things I tried: 1: I tried a few things without any knowledge like adding padding and margin to the last/first tab items. Made no changes. Also, the tab indicator is not an issue for me. I have removed it by setting transparent color and also the text size and font of the selected tab can be changed in the code in the selected and unselected callbacks.

  1. For the animations, I'm setting onTouch listener on the tab items in code with the thought that after extracting the motion direction, I can change the text scale factor. But the motion events stop after triggering after few times even if I'm moving the tab.

Moreover, using this logic, I change the text of the tab item that is being used to scroll. It will not provide the effect to each of the tabs that pass through the center. ** Is there a way to get this kind of tab layout?**

Below is my experimental code with no logic, but only with a starter code with the callback event:

        contentBinding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab?) {
                if (tab != null) {
                    Log.d("TAB_LAYOUT", "onTabSelected: "+tab.text)
                    //tab.view. ...
                    // Set huge text size and font
                }
            }

            override fun onTabUnselected(tab: TabLayout.Tab?) {
                if (tab != null) {
                    Log.d("TAB_LAYOUT", "onTabUnselected: "+tab.text)
                    //tab.view. ...
                    // Set normal unselected text size and font
                }
            }

            override fun onTabReselected(tab: TabLayout.Tab?) {
                if (tab != null) {
                    Log.d("TAB_LAYOUT", "onTabReselected: "+tab.text)
                    //tab.view. ...
                    // Set huge text size and font
                }
            }
        })

        for (i in 0..4){
            val mytab = contentBinding.tabLayout.getTabAt(i)
            mytab?.view?.setOnTouchListener(object : View.OnTouchListener {
                override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                    Log.d("TAB_ITEM_ALL"+i, "onTouch: "+event.toString())
                    if (event != null) {
                        if(event.action == ACTION_MOVE)
//                            if(event.x > prevX){
//                              moving away logic
//                            }
//                            else{
//                              moving towards
//                            }
                            return false
                    }
                    return false
                }
            })
        }

This code is totally flawed! It is not even the recommended method, I assume!!!

Should I use animation or translation or etc?

Any kind of help will be appreciated.


Solution

  • So, I solved it out myself without using TabLayout. Used a HorizontalScrollview, placed TextViews in it. And managed the animation by increasing/decreasing the scale of the views as the textviews move away/closer to the center.

    The pending part is to scroll them programmatically to align with center when the scroll has stopped. So that at any point of time (when not being scrolled), one of the textview has its center at the screens center.

    Here is the code:

        // Get the mid point of the screen
        val width = Resources.getSystem().displayMetrics.widthPixels/2
    
        // When at centre, thews will be 1.7x the original
        val maxScale = 1.7f
        val diffScale = maxScale - 1.0f
        
        // Have put the text views in list, as i have to perform the same calculations for each of the textviews
        val tabs = listOf(binding.textView11, binding.textView10, binding.textView9,
            binding.textView8, binding.textView7, binding.textView6,
            binding.textView5, binding.textView4, binding.textView3,
        )
    
        // Making a list of textview along with its width, used to calculate the center of the view
        val myTabs = mutableListOf<MyTab>()
        for (tab in tabs){
            val newTab = MyTab()
            newTab.view = tab
            tab.post {
                newTab.width = (tab.width.toFloat()*maxScale)/2
            }
            myTabs += newTab
        }
    
        binding.horizontalScrollView.isSmoothScrollingEnabled = true
    
        // scroll change listener, to perform calculations whenever the views move 
        binding.horizontalScrollView.setOnScrollChangeListener(
        (View.OnScrollChangeListener { v, _, _, _, _ ->
            run {
                
                for (tab in myTabs){
                    
                    // get the mid point of the view = (starting loc + width/2)
                    val loc = IntArray(2)
                    tab.view.getLocationInWindow(loc)
                    val viewMid = loc[0] + tab.width
    
                    // logic to scale the views as the mid point moves away from the center of the screen
                    // max scale, when view at center = 1.7x
                    // min scale when view away from center = 1.0x (no scale)
                    val diff = abs(width-viewMid)
                    if(diff <= tab.width) {
                        val p = (diff / tab.width)
                        val pr = p * diffScale
                        val change = max(maxScale - pr, 1.0f)
                        tab.view.scaleX = change
                        tab.view.scaleY = change
                    }
    
                }
    
            }
        })
        )