Android/Kotlin - LAGGY Checking item in BottomNavigationView from ViewPager - android

I am fairly new to programming, and this is my first large/real project.
--Background--
I have a ViewPager set up within my MainActivity, that slides between 3 different fragments. In Addition, one fragment requires menu items within the toolbar, and a BottomNavigationView is held within the MainActivity.
Each time i move to a new item/page of the ViewPager, i want to select(check) the corresponding page item within the BottomNavView.
I have attempted multiple configurations of the one below, but can't get it perfect
--The Problem--
The time it takes to perform bottom_navigation.menu.getItem(pos).isChecked = true is too long... Approx 50ms on my HTC One, or 100ms in the emulator.
This delay causes the ViewPager scrolling action to be jarred/laggy.
I would like to know if there is a different way to check or highlight the corresponding item within the BottomNavBar, or more desirably, perform the checking action at the same time without impeding the scrolling animation of the ViewPager.
--Attempted Fixes--
Perform check within onPageSelected - (Result is slightly more laggy)
Perform check when scroll state is IDLE - (Results in smooth pager scroll, but the checked item is 'left behind' and updated too late, particularly when scrolling fast)
Find the navigation item in Anko's doAsync{ } - (Results don't change)
// Adds a page-change listener
viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
// Set Toolbar title and menu items
override fun onPageSelected(position: Int) {
when (position) {
0 -> {
supportActionBar!!.title = "Page1"
toolbar.menu.setGroupVisible(0, false)
}
1 -> {
supportActionBar!!.title = "Page2"
if (toolbar.menu.size() == 0) {
toolbar.inflateMenu(R.menu.toolbar_list_menu)
} else {
toolbar.menu.setGroupVisible(0, true)
}
}
2 -> {
supportActionBar!!.title = "Page3"
toolbar.menu.setGroupVisible(0, false)
}
}
}
// While view pager settling on new item/page, check the correct bottom navigation view item
override fun onPageScrollStateChanged(state: Int) {
if (state == ViewPager.SCROLL_STATE_SETTLING) {
val pos = viewPager.currentItem
bottom_navigation.menu.getItem(pos).isChecked = true
}
}
// Not needed, must be implemented
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
})
I really hope someone has an answer to help with my apps performance :)
Thanks for your time.

Related

setting WKInterfacePicker height with animation selects arbitrary item

In a watchOS 2 interface controller, I am showing a button and a WKInterfacePicker.
When the user presses the button, the picker hides and shows with animation. The visual effect works nicely.
This has an unintended side effect: setting the height with animation changes the selected Item and I found no way around it so far.
func setDurationPickerVisibility(duration: NSTimeInterval) {
print ("self.durationPickerHidden=\(self.durationPickerHidden)")
animateWithDuration(duration) {
if self.durationPickerHidden {
self.durationPicker.resignFocus()
self.durationPicker.setHeight(0.0)
self.durationPicker.setHidden(self.durationPickerHidden)
} else {
self.durationPicker.setHeight(self.durationPickerHeigth)
print ("animation setting durationPicker Index to \(self.durationPickerIndex)")
self.durationPicker.setSelectedItemIndex(self.durationPickerIndex)
}
}
if !self.durationPickerHidden {
print ("direct setting durationPicker Index to \(self.durationPickerIndex)")
self.durationPicker.setSelectedItemIndex(durationPickerIndex)
self.durationPicker.setHidden(self.durationPickerHidden)
self.durationPicker.focus()
self.durationPicker.setSelectedItemIndex(durationPickerIndex)
}
}
#IBAction func durationPickerChanged(value: Int) {
print("durationPickerChanged: \(value)")
//...
}
when running this code, the console shows the following output:
self.durationPickerHidden=false
direct setting durationPicker Index to 24
animation setting durationPicker Index to 24
durationPickerChanged: 21
You see the picker changed to an index 21 that I did not set.
I tried many variations of this code, the crucial part seems to be the following:
When I am running without animation, everything works fine. (index 24 is selected)
it makes no difference if setFocus() is inside or outside the animation.
it makes no difference if setSelectedItemIndex is inside or outside the animation
when self.durationPicker.setHeight(self.durationPickerHeigth) is inside the animation (which is critical for the visual effect), then the picker selects this arbitrary Item 21.
Does anybody know how I can set the height of a picker with animation and still be in control which item is selected?
I found the following workaround that makes the problem invisible:
var durationPickerWorkaroundNecessary = false
func setDurationPickerVisibility(duration: NSTimeInterval) {
print ("self.durationPickerHidden=\(self.durationPickerHidden)")
animateWithDuration(duration) {
if self.durationPickerHidden {
self.durationPicker.resignFocus()
self.durationPicker.setHeight(0.0)
self.durationPicker.setHidden(self.durationPickerHidden)
} else {
self.durationPicker.setHeight(self.durationPickerHeigth)
print ("animation setting durationPicker Index to \(self.durationPickerIndex)")
self.durationPicker.setSelectedItemIndex(self.durationPickerIndex)
self.durationPicker.setHidden(self.durationPickerHidden)
self.durationPicker.focus()
self.durationPickerWorkaroundNecessary = true
}
}
}
#IBAction func durationPickerChanged(value: Int) {
print("durationPickerChanged: \(value)")
if durationPickerWorkaroundNecessary == true {
durationPickerWorkaroundNecessary = false
if durationPickerIndex != value {
print("durationPickerChanged: fixing value")
durationPicker.setSelectedItemIndex(durationPickerIndex)
return
}
}
durationPickerIndex = value
//...
}
The basic idea is to ignore the first change event after the animation.
Ugly, but it works.
Setting focus() inside the animation makes the animation smoother.

Animating NSTabView Views

I am trying to animate each view of NSTabView with a slide in when the view is selected.
I have this working in a fashion, but it only animates the first time i select a new tab view. After that i do not see an animation when switching tab views, although i can see the the function is fired every time.?
override func tabView(tabView: NSTabView, didSelectTabViewItem tabViewItem: NSTabViewItem) {
tabViewItem.view!.setFrameOrigin(NSPoint(x: tabViewItem.view!.frame.origin.x + 300, y: tabViewItem.view!.frame.origin.y))
tabViewItem.view!.animator().setFrameOrigin(NSPoint(x: tabViewItem.view!.frame.origin.x - 300, y: tabViewItem.view!.frame.origin.y))
// i can see this fires every time i switch tab views but the animation only works the fist time
}
I have changed it a little to solve first time problem. Just define an integer to store previous tab index in your delegate.
- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem{
CABasicAnimation *controlPosAnim = [CABasicAnimation animationWithKeyPath:#"position"];
if (prevTabViewIndex>[tabView indexOfTabViewItem:tabViewItem]) {
[controlPosAnim setFromValue:[NSValue valueWithPoint:CGPointMake(tabViewItem.view.frame.origin.x - 500,tabViewItem.view.frame.origin.y)]];
} else {
[controlPosAnim setFromValue:[NSValue valueWithPoint:CGPointMake(tabViewItem.view.frame.origin.x + 500,tabViewItem.view.frame.origin.y)]];
}
[controlPosAnim setToValue:[NSValue valueWithPoint:CGPointMake(tabViewItem.view.frame.origin.x,tabViewItem.view.frame.origin.y)]];
[[tabViewItem.view layer] addAnimation:controlPosAnim forKey:#"controlViewPosition"];
[tabViewItem.view setAnimations:[NSDictionary dictionaryWithObjectsAndKeys:controlPosAnim, #"frameOrigin", nil]];
[tabViewItem.view.animator setFrameOrigin : CGPointMake(tabViewItem.view.frame.origin.x,tabViewItem.view.frame.origin.y)];
prevTabViewIndex = [tabView indexOfTabViewItem:tabViewItem];
}

Sencha Touch 2 How to Detect Top Level on Nestedlist

I would like to create an event listener to detect when my nestedlist is at the top level, and then hide a component on the page. For example:
onNestedlistActiveItemChange: function(container, value, oldValue, options) {
if (this.getMyNestedList().atLevel(0)) <-- seudo code, does not work
{
Ext.getCmp('myButton').hide();
}
}
Thanks in advance for your help
The _backButton._hidden property of the nested list will return true when the default back button is hidden and false when the button is displayed. The button is hidden only when the nested list is at the top level.
The trick is to use the nested list's "back" event, which returns the state of the toolbar after the back event has occurred:
onMynestedlistBack: function(nestedlist, node, lastActiveList, detailCardActive, options){
if(nestedlist._backButton._hidden) {
Ext.ComponentQuery.query('#myButton')[0].hide();
}
}
That is, if the nested list's back button is hidden, then the list is at the top level, so hide myButton.
There are certainly more rigorous ways to do it, but most of them would involve overriding the toolbar's default button handling.
You can get the list level by checking the index of the list
var idx = nestedlist.getInnerItems().indexOf(list);
if (idx === 0) {
// top level...
}
[UPDATE]
You can also check the depth level of the record
var depth = record.getData().depth;
if (depth === 1) {
// top level...
}

Stop refreshing the bottom navigation bar fragments

I have a bottom navigation bar with 5 items. I want the last button to show a "More" menu. (Hence it should act as a button and not get into a selected state).
For this I'm keeping the last selected item id from the navigation bar and when a menu item is clicked setting the navigation item to the last selected item as follows.
menu_item_1_layout.setOnTouchListener { view, motionEvent ->
bottomSheetBehaviour.state = BottomSheetBehavior.STATE_HIDDEN
navigation.selectedItemId = lastSelectedItemId // views are refreshed here
true
}
My problem is how to stop the refreshing the last selected view(since it's already visible) when navigation item is set again. Since the menu is a BottomSheet, the view refresh is visible as a blinking. Or is there a better way to do this?
Ok. I found out that this behavior was because I'm always setting the fragment without checking the current selected fragment. When I added a check it works correctly.
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
if (item.itemId == lastSelectedItemId) { // added this
return#OnNavigationItemSelectedListener true
}
when (item.itemId) {
R.id.navigation_home -> {
val fragment = TestFragment()
supportFragmentManager.beginTransaction()
.replace(frame.id, fragment)
.commit()
lastSelectedItemId = item.itemId
return#OnNavigationItemSelectedListener true
}

Implementation of ActionMode.Callback

I have an Implementation of ActionMode to display the number of multi
selected items in a RecyclerView.
I would like to know when the back button in the actionMode is tapped so as to correspondingly reset the recyclerView but while implementing the ActionMode.Callback, i noticed that onDestroyActionMode is called whenever ActionMode is updated thus actionMode?.setTitle($selectedItems.size), which makes it impossible reset the recyclerView - remove selected items, remove overlay color and notify the recyclerview of data set changed.
Here's my Callback
inner class ActionModeCallback : ActionMode.Callback {
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
when (item?.getItemId()) {
R.id.action_delete -> {
myAdapter?.deleteSelectedIds()
actionMode?.setTitle("") //remove item count from action mode.
actionMode?.finish()
return true
}
}
return false
}
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
val inflater = mode?.getMenuInflater()
inflater?.inflate(R.menu.action_mode_menu, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
menu?.findItem(R.id.action_delete)?.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
return true
}
override fun onDestroyActionMode(mode: ActionMode?) {
Log.d(TAG, "onDestroyActionMode Called")
//myAdapter?.selectedIds?.clear()
//myAdapter?.notifyDataSetChanged()
actionMode = null
}
}
How Can i know when the ActionMode back button is tapped?
Full source code Here =>https://github.com/Edge-Developer/RecyclerViewMultiSelectExample
Holy Gosh. It was my fault, i was starting a New ActionMode each time an item is selected (through an interface on MainActivity) instead of checking if it has already been started before starting a new One.
Here was my code
actionMode = startActionMode(ActionModeCallback())
Here is the updated code
if (actionMode == null) actionMode = startActionMode(ActionModeCallback())
now, the onDestroyActionMode is called only once, after the actionMode is dismissed!
You can check the github repo on how's it was implemented
This problem was faced while implementing multiselection on a recyclerView.

Resources