I'm trying to abstract my Navigation Drawer to streamline my Android app but I'm running into some issues. The Nav Drawer simply doesn't respond to touches, I can't figure out why.
MainActivity.kt
class MainActivity : NavActivity() {
/** ON CREATE **/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
main_seekbar_price!!.setOnSeekBarChangeListener(this)
mDrawerLayout = findViewById(R.id.drawer_layout) // Variable in superclass
...
NavDrawer.kt
abstract class NavActivity : BaseActivity() {
/** Variables **/
lateinit var mDrawerLayout: DrawerLayout
/** ON CREATE **/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_nav)
// Set Nav Drawer listener
nav_view.setNavigationItemSelectedListener { menuItem ->
Log.d("NAV", "nav selected listener header")
menuItem.isChecked = true
mDrawerLayout.closeDrawers()
navMenuSwitch(menuItem)
true
}
// Set Nav Footer listener
nav_footer.setNavigationItemSelectedListener { menuItem ->
Log.d("NAV", "nav selected listener footer")
menuItem.isChecked = false
mDrawerLayout.closeDrawers()
navMenuSwitch(menuItem)
true
}
}
...
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorBackground"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" >
...
</FrameLayout>
<include layout="#layout/activity_nav" />
</android.support.v4.widget.DrawerLayout>
activity_nav.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.NavigationView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:menu="#menu/drawer_header"
app:headerLayout="#layout/nav_header">
<android.support.design.widget.NavigationView
android:id="#+id/nav_footer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:menu="#menu/drawer_footer">
</android.support.design.widget.NavigationView>
</android.support.design.widget.NavigationView>
I thought it has something to do with assigning mDrawerLayout in the child class but that didn't fix anything. It never calls the listeners though, no matter how much you click it it won't even print Logs in that listener.
Any help is welcome as I can't figure this out for the life of me, thank you in advance!
Four things happen, in this order, when your MainActivity's onCreate() is called:
MainActivity's content view is set by inflating activity_nav.xml.
The nav_view and nav_footer Kotlin variables are assigned by searching the content view for them. (Kotlin does this for you, so it's easy to forget that it's happening.)
Listeners are attached to the nav_view and nav_footer views.
MainActivity's content view is set again, this time by inflating activity_main.xml. (The include tag inside activity_main.xml tells the framework that it should inflate another copy of activity_nav.xml as part of the process.) The old content view is overwritten.
So by the time MainActivity.onCreate() finishes, the views with listeners attached are gone. One way to fix the problem would be to delete the line setContentView(R.layout.activity_nav) from NavActivity.onCreate() and to move the rest into an initNavDrawerListeners() method that's called from MainActivity.onCreate().
Related
I have an activity and a few fragments in it, and I have an action bar that I need to be visible all the time except for the case when I open MyTestFragment, so when I open this fragment I need my action bar to be hidden.
As my Activity is AppCompatActivity, I tried to call this method (from Activity) in order to hide an actionBar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
supportActionBar?.hide()
...
and it works, however, I need to make this call from fragment, so I do it like this
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
(activity as AppCompatActivity).supportActionBar!!.hide()
}
and the result is
The action bar became kind of invisible instead of gone (you see this gray pass right under the clock)
What am I missing here?
UPD
I would like to add explanation to the problem, in order to illustrate this I did such code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
Thread {
Thread.sleep(8000)
mainExecutor.execute {
supportActionBar?.show() <---- 2
}
}.start()
Thread {
Thread.sleep(12000)
mainExecutor.execute {
supportActionBar?.hide() <---- 3
}
}.start()
supportActionBar?.hide() <---- 1
...
What is happening here is - the method marked 1 is invoking first and the actionbar hides as expected. The next method #2 invokes show() for the action bar and the action bar appears as expected, however, when it comes to method #3 and tries to hide the actionbar again the bug is here. Instead of hiding the actionbar it kind of makes it invisible and you can see a grey line (like on the screenshot).
So, looks like it is possible to hide the actionbar only immediately in OnCreate() method if you try to do it after you will see a grey line instead of the actionbar.
UPD
My MainActivity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/awl_application_drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/main_activity_layout_bg_color"
tools:openDrawer="end">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<application.widget.toolbar.BondToolbarLayout
android:id="#+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<FrameLayout
android:id="#+id/errorViewContainer"
android:layout_below="#id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<FrameLayout
android:id="#+id/fragment_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/toolbar_layout"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
android:id="#+id/tab_layout"
style="#style/AppTabLayout"
android:layout_width="match_parent"
android:layout_height="#dimen/bottom_nav_tab_height"
android:background="#color/deprecated_palette_FF000000"
android:elevation="6dp"
android:fillViewport="true"
android:padding="0dp"
app:tabPaddingBottom="0dp"
app:tabPaddingEnd="0dp"
app:tabPaddingStart="0dp"
app:tabPaddingTop="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:tabMinWidth="#dimen/bottom_nav_tab_min_width" />
<CustomView
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="#id/tab_layout"
app:layout_constraintTop_toTopOf="parent"
app:theme="#style/ThemeOverlay.AppCompat.Light" />
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="#+id/navigation_view_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_marginTop="?android:attr/actionBarSize" />
</androidx.drawerlayout.widget.DrawerLayout>
When navigating to TestFragment, do supportActionBar?.hide() from Activity.
Do supportActionBar?.show() from Activity when navigating to the Fragment where the ActionBar should be visible.
I've done something like this in a project but on an Activity but you can do the similar thing on Fragments also
Here I've created a method called "setupToolbar" which I call from onCreate right after setContentView(R.layout.___)
private fun setupToolbar() {
toolbar.title = ""
setSupportActionBar(toolbar)
toolbar.navigationIcon = ContextCompat.getDrawable(this, R.drawable.arrow_back_white_24)
toolbar.setNavigationOnClickListener {
finish()
}
supportActionBar?.hide()
}
Then I added a gesture listener and observing SingleTap
private var gestureListener: GestureDetector.SimpleOnGestureListener =
object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent?): Boolean {
if (toolbar.visibility == View.VISIBLE) {
supportActionBar?.hide()
return true
} else {
supportActionBar?.show()
}
return false
}
}
There might be two options here.
Defining topbar in each fragment layout and don't include in specific fragment where not required, rather than including in activity.
OR
Define some NO ACTION BAR styles in style.xml, then set/unset it in java/kotlin code, when navigating to/from some fragment.
In styles.xml :
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
In kotlin/java code from activity:
Call setTheme method with above defined style and then call recreate()
In my kotlin project, I want to refresh fragments from an Actvity. When I finish Activity, it should refresh the fragment. I was thinking this would be done in onResume. So how to do refresh current fragment when I resume an activity? My fragment page contains listviews, hence it should be updated, when i finish activity.
try this
Use Swipe to refresh layout
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listView"
/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var swipeRefreshLayout: SwipeRefreshLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout)
swipeRefreshLayout.setOnRefreshListener {
//TODO operation
}
}
}
I have a basic app, containing a drawer layout. Each option in the drawer opens up a new fragment page for the user to view. Here is the drawer layout code.
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="#layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header_main"
app:menu="#menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
Here is the app_bar_main.xml code.
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DashBoardActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="#layout/content_main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And here is the content_main.xml. And also the main activity DashBoardActivity.kt
class DashBoardActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dashboard)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
val navView: NavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.nav_company_emissions, R.id.nav_your_emissions, R.id.nav_contact,
R.id.nav_privacy_policy
), drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)
return true
}
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
From my understanding when you click a button in the drawer layout the fragment in the host fragment is replaced with the relevant clicked on fragment.
My questions is... How do I programatically put another fragment in this nav host view which hasn't been set up with the drawer layout. My problem is one of the fragments contains a profile for a user with an edit button. I want to open a new fragment when the edit button is clicked and wish for the user to still have access to the drawer when they do so don't want to go to another activity.
In my mind I just want to replace the profile_fragment with the edit_fragment. But when I've tried something like this below its placed the fragment onto of the profile one so that you have this weird looking screen with two fragments on top of one another.
val frag: Fragment = EditYourEmissionsFragment()
val fragMan:FragmentManager = activity!!.supportFragmentManager
val fragTran: FragmentTransaction = fragMan.beginTransaction()
fragTran.add(R.id.nav_host_fragment, frag)
fragTran.addToBackStack(null)
fragTran.commit()
I'm still a relative novice when it comes to Android and Kotlin so am struggling to understand how I would go about doing my user case.
Scenario for trying to help paint a picture of what I want to happen:
User loads app and lands on home_fragment.
Opens drawer menu, clicks profile_fragment.
Once in profile_fragment opens the user presses edit.
A new fragment takes over the screen, but retains the ability to open the menu drawer.
User can now either open the drawer menu and navigate to other places across the app. Or save their profile and get taken back to the profile_fragment.
Thank you in advance for any help. Apologies if its not particularly clear.
include the EditYourEmissionsFragment in the navigationGraph like this:
<fragment
android:id="#+id/EditYourEmissionsFragment"
android:name="com.example.application.EditYourEmissionsFragment"
android:label="Edit Profile"
tools:layout="#layout/fragment_edit_omissions"/>
and then on your ProfileFragment set the onClick to open the destination like this;
val navController = Navigation.findNavController(view)
navController.navigate(R.id.EditYourEmissionsFragment)
and in your MainActivity
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.my_nav_host)
return navController.navigateUp(myAppBarConfiguration) || super.onSupportNavigateUp()
}
I have an activity with a CoordinatorLayout which contains a CollapsingToolbarLayout. I have bind the title of the activity to the title property of CollapsingToolbarLayout, like shown below(posting only relevant parts of code since the layout is too big):
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable name="viewModel"
type="com.android.myapp.ui.activity.home.HomeActivityViewModel"/>
</data>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.android.myapp.ui.behavior.scroll.appbar.DisableableAppBarLayoutBehavior"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:title="#{viewModel.pageTitle}"
app:collapsedTitleTextAppearance="#style/CollapsedAppBarTitleStyle"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleGravity="bottom|center_horizontal"
app:expandedTitleMarginBottom="85dp"
app:expandedTitleTextAppearance="#style/ExpandedAppBarTitleStyle"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<!--Remaining layout-->
</layout>
As you can see the pageTitle property is bind to the xml CollapsingToolbarLayout title property.
Now in the HomeActivityViewModel:
class HomeActivityViewModel(application: Application) : AndroidViewModel(application) {
var isToolbarExpandable = MutableLiveData<Boolean>() // For handling the expansion of toolbar on navigation. This works perfectly
var pageTitle = MutableLiveData<String>()
init {
isToolbarExpandable.value = true
pageTitle.value = "Home"
}
}
And in HomeActivity:
class HomeActivity : AppCompatActivity() {
private lateinit var homeViewModel: HomeActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivitySafaricomHomeBinding>(this, R.layout.activity_safaricom_home)
binding.lifecycleOwner = this
setSupportActionBar(toolbar)
homeViewModel = getHomeViewModel()
binding.viewModel = homeViewModel
}
private fun getHomeViewModel(): HomeActivityViewModel = ViewModelProviders.of(this).get(HomeActivityViewModel::class.java)
}
The page title is set correctly in this case. But the trouble starts when I try setting the title anytime after the Activity and the first child fragment is created(I am using NavHostFragment, so the first fragment is created and attached as soon as the activity is created). The only place I can change the the title is the onCreate on activity and onCreate life cycle methods of start fragment. I am using the viewmodel as a shared viewmodel to change title from all child fragments. I am getting the ViewModel in Fragments using:
ViewModelProviders.of(activity!!).get(HomeActivityViewModel::class.java)
After that, whenever I try to change the title, it simple won't change. Whether it be on a button click, after a delay, on the next fragment, nothing works. The only working place is onCreate of Activity till onCreateView of first fragment. I added an observer for pageTitle in HomeActivity, but after the first change, even the observer is not being triggered. On debugging, I could see that it is the same instance of viewmodel that is retrieved, and the pageTitle is being changed actually, but it simply stops observing after creation. Any idea what is the issue here? Please help.
LiveData doesn't work with DataBinding since it doesn't have a lifecycle.
After
binding.viewModel = homeViewModel
Try Adding
binding.lifecycleOwner = this
So, all together
binding.viewModel = homeViewModel
binding.lifecycleOwner = this
i want remove this space from navigation drawer
I want remove scroll mode from Navigation Drawer or any solution for create custom navigation drawer without menu
Here is my layout code.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
tools:openDrawer="end">
<include
layout="#layout/app_bar_live"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
app:headerLayout="#layout/nav_header_live"
android:overScrollMode="never" />
</android.support.v4.widget.DrawerLayout>
Creating a truly custom NavDrawer is possible, with little tweaking to the components.
Here are the steps:
1 - No menu layout, just headerView
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view_end"
android:layout_width="match_parent"
android:layout_marginLeft="-64dp"
android:layout_marginStart="-64dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:fitsSystemWindows="true"
app:headerLayout="#layout/shop_list"
/>
Notice that i placed margin start and left to -64dp. That's to get the drawer layout to be full width.
2 - Disable NavigationMenuView's scroll
Even though we didn't put a menu layout, it's still created and we need to access it and disable the scroll. Because this view is instance of the RecyclerView, we just need to override it's layout manager and disable the vertical scroll.
private fun disableMenuScroll(navView: NavigationView) {
val navMenu = navView.getChildAt(0) as NavigationMenuView
navMenu.layoutManager = object : LinearLayoutManager(this) {
override fun canScrollVertically(): Boolean {
return false
}
}
}
3 - Set the header layout to be full height
Here we use reflection to get the presenter from HeaderView and from that presenter we get the LinearLayout that holds our headerLayout. That LinearLayout is the one that doesn't let our layout stretch to match parent. We just change the LayoutParams for that LinearLayout and it's done.
private fun changeDrawerLayoutHeight(navView: NavigationView) {
/*With reflection get the navView's presenter*/
val field = navView.javaClass.getDeclaredField("presenter")
field.isAccessible = true
val presenter = field.get(navView) as NavigationMenuPresenter
/*From presenter, get the header layout field*/
val layoutField = presenter.javaClass.getDeclaredField("headerLayout")
layoutField.isAccessible = true
val headerLayout = layoutField.get(presenter) as LinearLayout
/*Set layout params on the HeaderLayout to match parent*/
val params = headerLayout.layoutParams
params.height = LinearLayout.LayoutParams.MATCH_PARENT
headerLayout.layoutParams = params
}
Where does this code execute?
In your Activity's onCreate method. Here is the code needed to execute this:
val navView = findViewById<NavigationView>(R.id.nav_view_end)
disableMenuScroll(navView)
changeDrawerLayoutHeight(navView)
Hope this helps somebody!
Try to add this to root tag (android.support.v4.widget.DrawerLayout)...hope it helps.
android:fitsSystemWindows="true"
and try removing that android:overScrollMode from both root tag and navigationview