I am using ActionBar.Tabs with ViewPager, and it looks like
I implemented the ActionBar.TabListener and ViewPager.OnPageChangeListener to support that when user swipe pages in ViewPager, the tab indicator will change accordingly (the blue indicator will move to the corresponding tab).
Now I realized that the blue indicator changes without any animation, and it doesn't look good. (when I swipe from tab1 to tab2, the blue indicator disappear under tab1 and appear under tab2). Is there a way to change it so that when I switch tabs, the blue tab indicator moves smoothly between tabs?
Use the TabLayout class from the design support library:
https://developer.android.com/reference/android/support/design/widget/TabLayout.html
It has a setupWithViewPager to easily connect it with a ViewPager, and it has a sliding indicator bar.
In my case it appeared because of TabSelectedListener. In tab_layout.addOnTabSelectedListener() I overrode a onTabSelected method, where I opened another activity without a delay.
view.tab_layout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab?.position == 1) {
MapsActivity.showScreen(this#ThisFragment)
}
}
})
I rewrote that method with a delay:
override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab?.position == 1) {
// Show tab animation and open an activity.
view.tab_layout.postDelayed({
// This check is optional.
if (isAdded && view.tab_layout.selectedTabPosition == 1) {
MapsActivity.showScreen(this#YourFragment)
}
}, 250)
}
}
Also I tried to change a duration value, but don't know how (didn't help):
view.tab_layout.animation = object: Animation() {}
view.tab_layout.animation.duration = 1000
view.tab_layout.layoutAnimation = LayoutAnimationController(object: Animation() {})
view.tab_layout.layoutAnimation.delay = 1000f
view.tab_layout.layoutAnimation.animation.duration = 1000
Related
I'm using the Exoplayer library to load videos in my application, but in certain videos I want to avoid that the user can't go forward in the video progress bar, only backward, until now I had achieved something by adding a listener to the DefaultTimeBar in this way:
var currentPosition: Long = 0
val exoPlayerProgressBar: DefaultTimeBar? = localPlayerView?.findViewById(
R.id.exo_progress
) as? DefaultTimeBar
exoPlayerProgressBar?.addListener(object: TimeBar.OnScrubListener {
override fun onScrubStart(timeBar: TimeBar, position: Long) {
currentPosition = position
}
override fun onScrubMove(timeBar: TimeBar, position: Long) {
if (currentPosition < position) {
timeBar.setPosition(position)
timeBar.setEnabled(false)
}
}
override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) {
timeBar.setEnabled(true)
}
})
But it doesn't work well at all, I think the listener takes a long time to be called and the position of the onScrubStart method is not exact and besides, although I manage to disable the user to advance, I haven't managed to do it if he clicks on a place on the bar. Is there another way that I don't know?
If you are using custom view for your xml then setting this in xml will work for androidx.media3.ui.DefaultTimeBar
app:touch_target_height="0dp"
I also got this answer from one of stackoverflow answer only but not having link as of now.
You should use ExoPlayer.Listener and override onPlaybackStateChanged() to detect state of playing video progress (Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED), use Player.STATE_READY to hide the progress bar and Player.STATE_BUFFERING to show the progress bar.
override fun onPlaybackStateChanged(playbackState: Int) {
when (playbackState) {
Player.STATE_ENDED -> {}
Player.STATE_READY -> hideProgressBar() //*Hide progressbar!
Player.STATE_BUFFERING -> showProgressBar() //*Show progressbar!
Player.STATE_IDLE -> {}
}
}
Listening to playback events
What I am trying to do is retrieve data from the server when I click a button. When I click the button, I want to show my "Loading..." TextView for 2 seconds, and only then show the data I got from the server. How can I do this?
For now my animation is working, but the data is showing almost instantly. I want to delay that. Using Thread.sleep(2000) causes both the data and Loading to be delayed.
val loadingAnimation :TextView = findViewById<TextView>(R.id.loadingAnimationTextView)
val alphaAnim = AlphaAnimation(1.0f, 0.0f)
alphaAnim.startOffset = 0
alphaAnim.duration = 2000
alphaAnim.setAnimationListener(object : AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
//not sure what code to put here
}
override fun onAnimationEnd(animation: Animation) {
// make invisible when animation completes, you could also remove the view from the layout
loadingAnimation.setVisibility(View.INVISIBLE)
}
override fun onAnimationStart(animation: Animation?) {
loadingAnimation.setVisibility(View.VISIBLE)
}
})
loadingAnimation.setAnimation(alphaAnim)
Thread.sleep(2000)
You can use the handler for this task.
Handler(Looper.getMainLooper()).postDelayed({
// Show you data here
loadingAnimation.setVisibility(View.INVISIBLE)
}, 2000)
Here, 2000 = 2 seconds
It's probably easier to use the ViewPropertyAnimator stuff:
loadingAnimation.animate()
.alpha(0)
.duration(2000)
.withEndAction {
// data displaying code goes here
}.start()
but honestly I don't think there's anything wrong with populating an invisible list, and just making it visible when you want to display it. But that up there is a way to chain runnable code and animations, one after the other
I have a simple fade out animation function for my android app, it works, but the issue I am having is that, after the fade out animation has run, the view (TextView in this case) does not stay faded out, the alpha value becomes 1 again.
Here is the fadeOut function code:
fun fadeOut(duration: Long = 100) : AlphaAnimation{
val fadeOut = AlphaAnimation(1f, 0f)
fadeOut.interpolator = DecelerateInterpolator()
fadeOut.duration = duration
return fadeOut
}
And I use it like this:
myTextView.startAnimation(fadeOut(500))
Any help or advice will be highly appreciated.
I think Animation::setFillAfter function would do the trick for you, the code would be like this:
val animation = fadeOut(500)
animation.fillAfter = true
myTextView.startAnimation(animation)
Although, this solution only preserves the alpha value of the view after the animation ends, if you want to change the visibility of the view, you need to change it when the animation ends using Animation.AnimationListener interface, the code would be like this:
myTextView.startAnimation(fadeOut(500).apply {
setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
myTextView.visibility = View.GONE
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
})
You can set visibility in handler after running animation
myTextView.startAnimation(fadeOut(500))
Handler().postDelayed({
myTextView.visibility = View.GONE
},500)
I am updating my app to Navigation Architecture Components and I see that it has a lag replacing fragments which is visible in the NavigationDrawer that does not close smoothly.
Until now, I was following this approach:
https://vikrammnit.wordpress.com/2016/03/28/facing-navigation-drawer-item-onclick-lag/
So I navigate in onDrawerClosed instead than in onNavigationItemSelected to avoid the glitch.
This has been a very common issue, but it is back again. Using the Navigation Component, it is laggy again and I don't see a way to have it implemented in onDrawerClosed.
These are some older answers prior to Navigation Component
Navigation Drawer lag on Android
DrawerLayout's item click - When is the right time to replace fragment?
Thank you very much.
I'm tackling this issue as I write this answer. After some testing, I concluded that code I'm executing in fragment right after its created (like initializing RecyclerView adapter and populating it with data, or configuring UI) is causing the drawer to lag as its all happening simultaneously.
Now the best idea I got is similar to some older solutions that rely on onDrawerClosed. We delay the execution of our code in fragment until the drawer has closed. The layout of the fragment will become visible before the drawer is closed, so it will still look fast and responsive.
Note that I'm also using navigation component.
First, we are going to create an interface and implement it fragments.
interface StartFragmentListener {
fun configureFragment()
}
In activity setup DrawerListener like:
private fun configureDrawerStateListener(){
psMainNavDrawerLayout.addDrawerListener(object: DrawerLayout.DrawerListener{
override fun onDrawerStateChanged(newState: Int) {}
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {}
override fun onDrawerOpened(drawerView: View) {}
override fun onDrawerClosed(drawerView: View) {
notifyDrawerClosed()
}
})
}
To notify a fragment that the drawer has been closed and it can do operations that cause lag:
private fun notifyDrawerClosed(){
val currentFragment =
supportFragmentManager.findFragmentById(R.id.psMainNavHostFragment)
?.childFragmentManager?.primaryNavigationFragment
if(currentFragment is StartFragmentListenr && currentFragment != null)
currentFragment.configureFragment()
}
In case you are not navigating to the fragment from the drawer (for example pressing back button) you also need to notify fragment to do its things. We will implement FragmentLifecycleCallbacksListener:
private fun setupFragmentLifecycleCallbacksListener(){
supportFragmentManager.findFragmentById(R.id.psMainNavHostFragment)
?.childFragmentManager?.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentActivityCreated(fm: FragmentManager, f: Fragment, savedInstanceState: Bundle?) {
super.onFragmentActivityCreated(fm, f, savedInstanceState)
if (!psMainNavDrawerLayout.isDrawerOpen(GravityCompat.START)) {
if (f is StartFragmentListener)
f.configureFragment()
}
}
}, true)
}
In fragment:
class MyFragment: Fragment(), MyActivity.StartFragmentListener {
private var shouldConfigureUI = true
...
override fun onDetach() {
super.onDetach()
shouldConfigureUI = true
}
override fun configureFragment() {
if(shouldConfigureUI){
shouldConfigureUI = false
//do your things here, like configuring UI, getting data from VM etc...
configureUI()
}
}
}
A similar solution could be implemented with a shared view model.
Avoid the lag caused while changing Fragment / Activity onNavigationItemSelected- Android
Navigation Drawer is the most common option used in the applications, when we have more than five options then we go towards the navigation menu.
I have seen in many applications that when we change the option from the navigation menu, we observe that it lags, some people on StackOverflow recommended that use Handler like the below code:
private void openDrawerActivity(final Class className) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
ProjectUtils.genericIntent(NewDrawer.this, className, null, false);
}
}, 200);
}
But in the above code, it’s still not smooth & I thought why we add handler may be there is another solution after so much R&D, what I figure it out that we need to change the fragment/activity when the drawer is going to be close. let’s see with the implementation.
For more details with solution kindly go through https://android.jlelse.eu/avoid-the-lag-caused-while-changing-fragment-activity-onnavigationitemselected-android-28bcb2528ad8. It really help and useful.
Hope you find better solutions in it!
setCurrentItem() function isnt working for following android(java) code how to make it work ?
Intent i = getIntent();
int position = i.getIntExtra("img", 0);
viewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
ImageAdapter adapter = new ImageAdapter(this);
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(position);
Frank is right. To provide the the scroll device independent, you should use onGlobalLayoutListener. Thereby, you can guarantee that the view is rendered when you call viewPager.setCurrentItem(position). Otherwise setCurrentItem won't have any effect since the view is not rendered yet.
viewPager.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
viewPager.setCurrentItem(position, false);
}
});
I know the question is already old but maybe someone else will find this usefull!
private ViewPager viewPager;
#Override
public void onResume() {
Intent i = getIntent();
int position = i.getIntExtra("img", 0);
viewPager.postDelayed(new Runnable() {
#Override
public void run() {
viewPager.setCurrentItem(position);
}
}, 100);
}
just adding a shorter answer with androidx using view extension in kotlin
view_pager.doOnLayout {
view_pager.setCurrentItem(position, false)
}
note that the previos answer using global layout is not removing the OnGlobalLayoutListener afterwords which is not advisable.
androidx ext doOnLayout does that.
Please, make sure you have a gradle dependency:
implementation 'androidx.core:core-ktx:1.3.2'
None of these answers worked for me - it took hours for me to find out that I'd forgotten to update getCount().
My getCount() method in my implementation of the FragmentPagerAdapter was displaying the incorrect count, so it could not change page since it was not part of its count.
This worked with me
if (mViewPager.getAdapter() != null)
mViewPager.setAdapter(null);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setCurrentItem(desiredPos);
Ok, for me, I have been troubleshooting this issue like for 3 hours stright and I got to a point that I made it work this way
Before, I had this inside my ViewPagerStateAdapter
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment {
return when (position) {
1 -> myFragmentInstance1()
else -> myFragmentInstance2()
}
}
Then for changing the pages I was using the following
private fun navigateTo1() {
binding.viewPager.setCurrentItem(1, false)
}
private fun navigateTo2() {
binding.viewPager.setCurrentItem(2, false)
}
First of all after initializing my viewpager adapter like this
private fun setupViewPager() {
binding.viewPager.apply {
adapter = MyViewPagerStateAdapter(this#MainFragment)
}
}
the initial position inside createFragment was 0 , so the else block was always called starting my viewpager with the inscance of myFragmentInstance2()
This is not all, after clicking and trying to setCurrentItem to another position the viewpager was changing only once and then it did not work anymore to switch pages
After long times of debug and research I tried to change the positions of the fragments like this
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> myFragmentInstance1()
else -> myFragmentInstance2()
}
}
which in first place worked for loading 0 as the default destination, and then switching with currentItem worked as expected
private fun navigateTo1() {
binding.viewPager.setCurrentItem(0, false)
}
private fun navigateTo2() {
binding.viewPager.setCurrentItem(1, false)
}
I also tried with
private fun navigateTo1() {
binding.viewPager.currentItem = 0
}
private fun navigateTo2() {
binding.viewPager.currentItem = 1
}
which puts a smoothTransition to true as default and is working fine.
So my conclusion is that the createFragment initializes our different fragments in a position-based way, starting from 0 and if we start creating our fragments at position 1 it will mess the order and then we will end up having just 1 fragment in the stack to switch.
I did lots of research on the issue but did not found anything related to this issue because if we use TabLayoutMediator this issue is not happening, it only happens when we try to switch the viewPager programatically with currentItem