How to hide bottom app bar on scroll in android - android

I am making an android application which has an activity and other activity implements the main activity.Now i am also implementing one activity many fragment pattern.So each activity has at least 7-8 fragment inside that.
Here is layout for my main activity.
<?xml version="1.0" encoding="utf-8"?>
<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=".MainActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="60dp"
android:id="#+id/frame_lay">
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_above="#id/bottom_appbar"
app:layout_anchor="#+id/bottom_appbar"
android:background="#android:color/darker_gray"/>
<com.google.android.material.bottomappbar.BottomAppBar
android:id="#+id/bottom_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:backgroundTint="#color/colorbottomappbar"
app:fabAlignmentMode="center"
app:navigationIcon="#drawable/ic_menu_green_24dp">
</com.google.android.material.bottomappbar.BottomAppBar>
<ImageButton
android:id="#+id/fab"
android:layout_width="190dp"
android:layout_height="80dp"
android:visibility="visible"
app:layout_anchorGravity="center_horizontal|bottom"
android:background="#drawable/logo"
app:layout_anchor="#+id/bottom_appbar"
android:layout_marginBottom="17dp"/>
<ImageButton
android:id="#+id/fab_two"
android:layout_width="190dp"
android:layout_height="80dp"
android:visibility="gone"
app:layout_anchorGravity="center_horizontal|bottom"
android:background="#drawable/logotwo"
android:elevation="5dp"
app:layout_anchor="#+id/bottom_appbar"
android:layout_marginBottom="13dp">
</ImageButton>
You can see that my main activity has frame layout in it in which i transact all the fragments.I used image button in place of floating action button as i want floating action button of oval shape.Now what i want that inside fragment when user scrolls then the image button , bottomappbar and the view which is horizontal line hides? The bottom app bar is being used in many fragments so i need a code which i can write in a activity which hides the bottomapp bar and the image button on while users scroll inside fragment.How can i achieve this? I am sorry for my silly question as i am new to android development .Thanks in advance.

You can achieve this by putting the below two lines in xml
app:hideOnScroll="true"
app:layout_scrollFlags="scroll|enterAlways"
So the full xml tag will be
<com.google.android.material.bottomappbar.BottomAppBar
android:id="#+id/bottom_app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:fabAlignmentMode="center"
app:hideOnScroll="true"
app:layout_scrollFlags="scroll|enterAlways"/>

Because you are scrolling inside a Fragment, you need to pass the scrolling values to your activity.
I suggest you use the default InteractionInterface that Android Studio generated in Fragment's template:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment_blank, container, false)
root.scrollView2.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
// the key is here.
var delta = scrollY - oldScrollY
listener?.onFragmentScrolled(delta)
}
return inflater.inflate(R.layout.fragment_blank, container, false)
}
interface OnFragmentInteractionListener {
// Name your function here
fun onFragmentScrolled(delta: Float)
}
// the lines below are generated,
// not the key point here but important to binding listener
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnFragmentInteractionListener) {
listener = context
} else {
throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener")
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
And then, in YourActivity, implement YourFragment.OnFragmentInteractionListener
Override the function
override fun onFragmentScrolled(delta: Float) {
anotherView.translationY = anotherView.translationY + delta
if (anotherView.translationY > anotherView.height)
anotherView.translationY = anotherView.height.toFloat()
if (anotherView.translationY < 0)
anotherView.translationY = 0f
}
the result will be like this mp4 link
The main point is : Pass your scrolling action from Fragment to Activity,
You can achieve this in many ways, this is just the basic one;

A similar approach to Wesely's is the following.
Let assume we have something like this:
A custom bottom appbar and we have at least one fragment per action.
What I mean is that activity main is the parent where all the fragments are going to place in.
You can declare an interface in main activity (in this example I want to hide/show a custom bottom appbar and FAB) to perform show/hide of the bottom appbar and fab (both of them in activity_main.xml), so the interface would look something like this:
class MainActivity : AppCompatActivity(), OnScrollListenerMain {
...
override fun fabAndBottomAppBarHide() {
if (fab_main.isVisible) {
fab_main.hide()
bottom_app_bar.performHide()
}
}
override fun fabAndBottomAppBarShow() {
if (!fab_main.isVisible) {
fab_main.show()
bottom_app_bar.performShow()
}
}
...
} // end Main
interface OnScrollListenerMain {
fun fabAndBottomAppBarHide()
fun fabAndBottomAppBarShow()
}
Once the interface is defined in main activity, every fragment with a Nested scroll view can implement it.
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nsv_fragment_with_scroll"
... >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
</LinearLayout>
</androidx.core.widget.NestedScrollView>
Creating a reference to the NestedScrollView and attaching the onScrollChangeListener will help us to check when there is a change in the scroll over the y axis
class FragmentWithScroll : Fragment() {
lateinit var mScroll:NestedScrollView
...
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_with_scroll,container,false)
mScroll = view.findViewById(R.id.nsv_fragment_with_scroll)
mScroll.setOnScrollChangeListener { v: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
val dy = oldScrollY - scrollY
if (dy < 0) {
(v!!.context as OnScrollListenerMain).fabAndBottomAppBarHide()
} else if (dy > 0) {
(v!!.context as OnScrollListenerMain).fabAndBottomAppBarShow()
}
}
...
return view
}
}
Remember in Kotlin we can use _ when the parameter in the lambda is never used.
So basically the change in y is val dy = oldScrollY - scrollY and dy is negative when the scroll is from bottom to top and this condition dy < 0 is true, so we use the context of the view to invoke OnScrollListenerMain.fabAndBottomAppBarHide()
and again dy is positive when the scroll is from top to bottom and this condition dy > 0 is true, so we use the context of the view to invoke OnScrollListenerMain.fabAndBottomAppBarShow()
But, what if we just want to hide a BottomAppbar (without hiding FAB)?
The BottomAppbar might be child of Coordinator layout and the fragments need to have a NestedScrollView, RecyclerView or ScrollView as parent in their XML, so the main activity XML should look something like:
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/coordinator_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/lly_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="#drawable/ic_add"
app:borderWidth="0dp"
app:layout_anchor="#+id/bottom_app_bar" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="#+id/bottom_app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:fabAlignmentMode="center"
app:hideOnScroll="true">
</com.google.android.material.bottomappbar.BottomAppBar>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
The fragments should be placed into lly_main and this line makes the trick: app:hideOnScroll="true"

You can achieve this by adding the following attribute to your BottomAppBar:
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"

Related

How do I keep the bottom part of the android app fixed while I switch activities like Instagram

I am trying to create an App like Instagram as part of a project and I want to keep the bottom part of the app fixed with the different Image Buttons that takes you to different parts of the App like home, messages and other functionalities.
I am currently making my app in a Relative Layout and will be switching to Constraint Layout after I get my App properly developed
I want the bottom part of the App with the Image Buttons fixed when a user clicks one of those buttons and is taken to some other type of layout xml file
Looks like you are talking about Bottom navigation bars :
Bottom navigation bars display three to five destinations at the bottom of a screen. Each destination is represented by an icon and an optional text label. When a bottom navigation icon is tapped, the user is taken to the top-level navigation destination associated with that icon.
With the Bottom navigation bars, you can switch fragments and have the navigation bar visible and fixed all over your app.
You can find a lot of information about how to create this, you can check this toturial, this video and many more.
You can use Bottom Navigation View or even a TabLayout anchored at the bottom of an activity.
You need an Activity that will host Fragments. These fragments will be displayed in turn as you interact with the Activity's hosting view.
I have created an Android Project to demonstrate. Sorry, you didn't specify language so i wrote it in Kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupViewPager(pager)
}
//the ViewPager will be responsible for navigating through your fragments even when you need to slide through them
private fun setupViewPager(viewPager: ViewPager){
val adapter = ViewPagerAdapter(supportFragmentManager)
setTabs(adapter)
// viewPager.offscreenPageLimit = 3
viewPager.adapter = adapter
initTabLayout()
}
//assign icons to the TabLayout
#SuppressLint("NewApi")
fun setTabIcons(){
tabLayout.getTabAt(0)!!.setIcon(ContextCompat.getDrawable(applicationContext,R.mipmap.ic_launcher))
tabLayout.getTabAt(1)!!.setIcon(ContextCompat.getDrawable(applicationContext,R.mipmap.ic_launcher))
tabLayout.getTabAt(2)!!.setIcon(ContextCompat.getDrawable(applicationContext,R.mipmap.ic_launcher))
}
#SuppressLint("NewApi")
fun initTabLayout(){
tabLayout!!.setupWithViewPager(pager)
setTabIcons()
}
//assign Fragments associated with a specific Tab Item
private fun setTabs(adapter:ViewPagerAdapter){
adapter.addFragment(FragmentOne(), "")
adapter.addFragment(FragmentTwo(), "")
adapter.addFragment(FragmentThree(), "")
}
}
class ViewPagerAdapter(manager: FragmentManager) : FragmentPagerAdapter(manager){
private val mFragmentList = ArrayList<Fragment>()
private val mFragmentTitleList = ArrayList<String>()
override fun getItem(position: Int): Fragment {
return mFragmentList[position]
}
override fun getCount(): Int {
return mFragmentList.size
}
fun addFragment(fragment: Fragment, title: String){
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
override fun getPageTitle(position:Int): CharSequence{
return mFragmentTitleList.get(position)
}
}
Your single fragment will look something like:
class FragmentOne : Fragment() {
lateinit var rootView:View
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
rootView = inflater.inflate(R.layout.fragment_view,container,false)
rootView.findViewById<TextView>(R.id.textView).setText("Fragment One")
return rootView
}
}
Your view will look something like this (in this case it is named fragment_view.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
<android.support.design.widget.TabLayout
android:id="#+id/tabLayout"
app:tabMode="fixed"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#FFF"
android:elevation="6dp"
android:layout_gravity="bottom"
app:tabGravity="center"
app:tabRippleColor="#color/colorAccent"
app:tabPaddingStart="35dp"
app:tabPaddingEnd="35dp"
app:tabBackground="#android:color/transparent"
app:tabTextColor="#color/colorPrimaryDark"
app:tabSelectedTextColor="#color/colorPrimaryDark"
app:tabIndicatorColor="#android:color/transparent"
android:minHeight="?attr/actionBarSize"
/>

RecyclerView in BottomSheet not working as expected

I have a problem with RecyclerView directly inside of layout with bottomsheetbehaviour. The problem is that when bottom sheet is expanded and content is scrolled down, when I go to scroll back up it causes Bottom Sheet to start collapsing, instead of RecyclerView first being scrolled back to top.
Here's a video to demonstrate the problem. As you can see the problem appears when I scroll down on expanded bottom sheet. It immediately start to collapse instead of "waiting" for RecyclerView to scroll to top first.
Here is my layout code
<?xml version="1.0" encoding="utf-8"?>
<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:id="#+id/scheduleRoot"
android:layout_height="match_parent"
tools:context=".schedule.ScheduleFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scheduleSheet"
app:behavior_peekHeight="300dp"
android:elevation="16dp"
android:clickable="false"
android:focusable="false"
android:background="#drawable/bg_bottom_sheet"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scheduleRecyclerView"
android:clickable="true"
android:focusable="true"
android:layout_marginTop="8dp"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Any help is appreciated!
I just encountered same problem, but I fixed it by adding this to onCreate:
androidx.core.view.ViewCompat.setNestedScrollingEnabled(recyclerview, false);
Add
android:nestedScrollingEnabled="true"
in the root layout of BottomSheetDialogFragment.
I had similar issue: Maybe the solution to my problem will give you some ideas. My bottom sheet was expanded to full height with recycler view in it; the bottom sheet was collapsing on user-drag, even though the first item in recycler view wasn't visible yet.
So, what I did:
You can enable/disable bottom sheet dragging by "isDraggable" = true/false
Add OnScrollListener for recycler view.
Override onScrolled and check layoutManager.findFirstVisibleItemPosition() in it
If first item is visible - update bottom sheet behavior.isDraggable = true, i also added small delay before setting behavior.isDraggable = true, because bottom sheet was collapsing too fast, but you might not need it
Maybe it's not optimal but it was fitting my needs and maybe will help you.
Your recyclerview item has overighted the scrolling state, so this error generates. The layout you provided does not have enough data to determine the cause. You change the item is a unique view to check
I played with this for a long time and tried way too many solutions. For me, this worked best:
val layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.setOnScrollChangeListener { _, _, _, _, _ ->
if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
bottomSheetBehavior.isDraggable = layoutManager.findFirstCompletelyVisibleItemPosition() == 0
} else {
bottomSheetBehavior.isDraggable = true
}
}
The key to the solution is is controlling users ability drag the bottom sheet while the recyclerview is partially scrolled. The method only allows scrolling again once the top most cell is fully visible.
Its not ideal as the user may want to grab the very top of the bottom sheet (assuming its not part of the recycler view) and dismiss the bottom sheet regardless of its scroll position. Im just accepting.
Whatever you do, do not try these, as they just disable any recycling functionality and all cells are loaded at instantiation having a really bad impact on performance:
wrap_content
or:
binding.recyclerView.isNestedScrollingEnabled = false
Enable the scroll state of BottomSheet to allow scroll if recyclerview 0th item is visible.
activity_main.xml
<layout 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">
<data />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#A8A7A7"
tools:context=".MainActivity">
<LinearLayout
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#fff"
android:orientation="vertical"
app:behavior_hideable="true"
app:behavior_peekHeight="80dp"
app:layout_behavior="com.asadmukhtar.recyclerviewinsidebottomsheet.LockableBottomSheetBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="Drag Me"
android:textColor="#000"
android:textSize="20sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_items"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
LockableBottomSheet file that used for handling allow dragging option or not.
class LockableBottomSheetBehavior<V : View?> : BottomSheetBehavior<V> {
private var mAllowUserDragging = true
constructor()
constructor(context: Context, attrs: AttributeSet?) : super(
context,
attrs
)
fun setAllowUserDragging(allowUserDragging: Boolean) {
mAllowUserDragging = allowUserDragging
}
override fun onInterceptTouchEvent(
parent: CoordinatorLayout,
child: V,
event: MotionEvent
): Boolean {
return if (!mAllowUserDragging) {
false
} else super.onInterceptTouchEvent(parent, child, event)
}
}
MainActivity.java
var bottomSheetBehavior: LockableBottomSheetBehavior<*>? = null
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
setUpBottomSheetBehaviour()
binding.rvItems.layoutManager = LinearLayoutManager(this)
binding.rvItems.adapter = RecyclerViewAdapter(this)
binding.rvItems.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val firstPosition = (binding.rvItems.layoutManager as LinearLayoutManager)
.findFirstVisibleItemPosition()
updateBottomSheetLockState(firstPosition == 0)
}
})
}
fun updateBottomSheetLockState(allow: Boolean) {
bottomSheetBehavior?.setAllowUserDragging(allow)
}
fun updateBottomSheetState(state: Int) {
if (bottomSheetBehavior != null) {
bottomSheetBehavior?.state = state
}
}
private fun setUpBottomSheetBehaviour() {
val bottomSheetBehavior: BottomSheetBehavior<LinearLayout> =
BottomSheetBehavior.from(binding.parent)
this.bottomSheetBehavior = bottomSheetBehavior as LockableBottomSheetBehavior<*>
updateBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED)
}
Your implementation might need more coding and with the provided code we might not able to give you good feedback.
Try this documentation
https://material.io/develop/android/components/bottom-sheet-behavior/
Plus I found this another implementation.
https://www.youtube.com/watch?v=WeaylHAwIIk

Why my foreach breaks when I try to draw dynamic buttons in Kotlin?

I need to draw dynamic buttons inside a foreach loop that retrieve data from my anko sqlite, the foreach only enter once and breaks and only draw one button in my layout, what I doing wrong? my code is this:
fun loadZones (ctx: Context, update: String, view: View, layout: LinearLayout) {
val zonesParser = rowParser{idzone: Int, zone: String -> Pair(idzone, zone)}
for (it in ctx.database.use {
select("tableplan")
.distinct()
.column("idzone")
.column("zone")
.orderBy("zone")
.parseList(zonesParser)
}) {
val layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
val btnZone = layoutInflater.inflate(R.layout.zones_item, null) as MaterialButton
btnZone.text = it.second
btnZone.id = it.first
layout.addView(btnZone, layoutParams)
Log.e("PAIR", "FIN DEL CICLO")
continue
}
}
The data that retrieves from my query is this:
(2, LARRY)
(1, MADISON)
That's my activity, I need to draw the buttons in "lytZonesButtons" id
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".TablePlanFragment">
<com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent"
android:layout_height="wrap_content" android:elevation="2dp"
tools:targetApi="lollipop" app:liftOnScroll="true">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbarTablePlan"
style="#style/com.madison.Toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="#string/table_title_module">
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_marginTop="56dp"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:orientation="horizontal"
android:background="#color/orangeLighter"
android:gravity="center_vertical"
android:padding="5dp" android:id="#+id/lytZonesButtons" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="112dp"
android:padding="5dp">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rc_tableplan"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</androidx.core.widget.NestedScrollView>
</FrameLayout>
and that's my button template that I called "zones_item":
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.button.MaterialButton
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" style="#style/com.madison.AppButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="#style/TextAppearance.MaterialComponents.Subtitle2"
tools:text="MADISON"
tools:targetApi="lollipop"
android:layout_margin="5dp"
/>
EDIT: I found the solution!
I don't now why my layout instance in the twice iteration of my loop throws NullPointerException but not shows in the log cat, my solution was put the loop code in onCreateView function, this is the code:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.activity_tableplan, container, false)
val iActivity = (activity as AppCompatActivity)
iActivity.setSupportActionBar(view.toolbarTablePlan)
iActivity.supportActionBar?.setDisplayShowTitleEnabled(true)
// view.rc_tableplan.setHasFixedSize(true)
// val gridLayoutManager = GridLayoutManager(context, 2, GridLayoutManager.HORIZONTAL, false)
// view.rc_tableplan.layoutManager = gridLayoutManager
val response = loadTablePlan(this.context!!, "no")
if (response.trim().toUpperCase() == "SUCCESS") {
val zonesParser = rowParser{idzone: Int, zone: String -> Pair(idzone, zone)}
for (zone in this.context!!.database.use {
select("tableplan")
.distinct()
.column("idzone")
.column("zone")
.orderBy("zone")
.parseList(zonesParser)
}) {
val layout:LinearLayout = view.lytZonesButtons
layout.let {
val btnZone = layoutInflater.inflate(R.layout.zones_item, layout, false) as MaterialButton
btnZone.text = zone.second
btnZone.id = zone.first
btnZone.requestLayout()
layout.addView(btnZone)
Log.e("PAIR", "FIN DEL CICLO")
}
}
}
return view
}
Thanks a lot for all people that tried help me, some admin can close my question please.
The hint is only one button is showing. Your trying to inflate the same view twice in the same spot.
You need to add an empty linearlayout in your xml. And in your loop change the buttonz..
var btnZone = findViewById(R.layout.btnZone)
button.text = "Pair"
btnZone.addView(button, layoutParams)
That's not the exact code (and probably not even the right syntax) but it shows you how you need to modify your loop.
Basicly you were attempting to inflate the same instance of the same view. When really your not inflating any views this way your just adding views.
Note
If you have a linearlayout in your xml when you add another button view to it it will add it below it. If you set the layout orientation to horizontal the button view then gets added beside the other one.
here's a link to an example.
Sorry I would make sure my code matched your code and variables with proper syntax but I am at work.

BottomSheetDialogFragment is not showing up

I've followed this tutorial to implement BottomSheetDiaogFragment in my android application.
this is my bottom sheet layout (bottom_sheet.xml):
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioGroup
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<RadioButton
android:id="#+id/rb1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_weight="1"
android:text="#string/rb1" />
<RadioButton
android:id="#+id/rb2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_weight="1"
android:text="#string/rb2" />
</RadioGroup>
</android.support.constraint.ConstraintLayout>
BottomSheetDialogFragment class:
class BottomSheetTaskRepeat : BottomSheetDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.bottom_sheet, container, false)
}
}
activity:
private val bottomSheetTaskRepeat = BottomSheetTaskRepeat()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bottomSheetTaskRepeat.show(supportFragmentManager, "my_bottom_sheet")
}
The problem is that the bottom sheet is not showing up! Any help is appreciated.
This is a late Answer, I am writing for anyone who will face the same problem, This is what I found:
For some reasons non-constrained views heights do not work in BottomSheetDialogFragment. A view whose height is like wrap_content will not show.(But the shadow will be there), but when you specify its height like 80dp it works.
For this question go and change your RadioGroup height and specify it like to:
android:layout_height="200dp"
Hope that is helpful.
UPDATE:
Since the default container of BottomSheetDailogFragment is FrameLayout and is set to WRAP_CONTENT, You can override that on your Fragment onStart method like this (Kotlin):
override fun onStart() {
super.onStart()
val containerID = com.google.android.material.R.id.design_bottom_sheet
val bottomSheet: FrameLayout? = dialog?.findViewById(containerID)
bottomSheet?.let {
BottomSheetBehavior.from<FrameLayout?>(it).state =
BottomSheetBehavior.STATE_HALF_EXPANDED
bottomSheet.layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT
}
view?.post {
val params = (view?.parent as View).layoutParams as (CoordinatorLayout.LayoutParams)
val behavior = params.behavior
val bottomSheetBehavior = behavior as (BottomSheetBehavior)
bottomSheetBehavior.peekHeight = view?.measuredHeight ?: 0
(bottomSheet?.parent as? View)?.setBackgroundColor(Color.TRANSPARENT)
}
}
Try following:
Create and use the parameterless static method newInstance in your BottomSheetDialogFragment and call the show() method on it.
Try LinearLayoutCompat as a root layout instead of ConstraintLayout.
Try a colourful background to the root layout to get an idea.
Try match_parent height for the root layout.
Make sure that the dismiss() or cancel() is not being called on it immediately.
Check visibility.
If it contains a recyclerView, make sure that it has items, getItemCount is not returning 0, and that we are setting up the values properly!
Restart both: PC and Device if your changes are not reflecting because of Android Studio errors.
Instead of using supportFragmentManager you have to use childFragmentManager,if you are inflating from inner fragments.
private val bottomSheetTaskRepeat = BottomSheetTaskRepeat()
bottomSheetTaskRepeat.show(childFragmentManager, "my_bottom_sheet")

Fragment with ViewPager having blank pages after returning to it from another fragment

In my application I'm trying to create 2 fragments with ViewPagers. The fragments have 3 tabs/pages each with RecyclerViews and they are supposed to be backed by a database, so I'm keeping them in a List.
class MainActivity : AppCompatActivity() {
val fragments = listOf(SwipeFragment(), SwipeFragment())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction().replace(R.id.placeholder, fragments[0]).commit()
button1.setOnClickListener {
supportFragmentManager.beginTransaction().replace(R.id.placeholder, fragments[0]).commit()
}
button2.setOnClickListener {
supportFragmentManager.beginTransaction().replace(R.id.placeholder, fragments[1]).commit()
}
}
}
The problem is that after navigating away from the SwipeFragment and going back, the view seems empty. Then, if you navigate to e.g. the leftmost page, the rightmost one "appears" (when you go to it).
This results in the middle page staying empty. (the reason for it being that FragmentStatePagerAdapter keeps only the current page and 2 adjacent ones by default. The middle one doesn't get refreshed in a layout with 3 tabs - I tried it also with 5 tabs and I'm able to bring all of them back by going back and forth).
After some debugging I saw that the fragments that represent pages don't get removed from the FragmentManager, but the main SwipeFragment is. I can't get my head around how FragmentManager really works, even after reading almost all of the source code.
If I were able to somehow safely remove the fragments from the FragmentManager, it may work.
I've tried some techniques for saving state, but the framework does that automatically (which might actually be the cause of this problem).
I'm just a beginner in Android, so there's probably a better way to do this anyway. I'll leave the rest of the files here for reference.
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.test.MainActivity">
<FrameLayout
android:id="#+id/placeholder"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:orientation="horizontal">
<Button
android:id="#+id/button1"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="button1" />
<Button
android:id="#+id/button2"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="button2" />
</LinearLayout>
</LinearLayout>
SwipeFragment.kt
class SwipeFragment : Fragment() {
// TODO: set up and keep the database here
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater!!.inflate(R.layout.fragment_swipe, container, false)
view.tab_layout.setupWithViewPager(view.pager)
view.pager.adapter = DummyPagerAdapter(activity.supportFragmentManager, index)
return view
}
}
fragment_swipe.xml
<android.support.v4.view.ViewPager
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/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.test.SwipeFragment">
<android.support.design.widget.TabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v4.view.ViewPager>
ItemListFragment.kt
class ItemListFragment() : Fragment() {
// Or keep the database here?
// Probably not the best idea though
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater!!.inflate(R.layout.fragment_item_list, container, false)
if (view is RecyclerView) {
view.layoutManager = LinearLayoutManager(view.context)
view.adapter = MyItemRecyclerViewAdapter(DummyContent.ITEMS)
}
return view
}
}
DummyPagerAdapter.kt
class DummyPagerAdapter(manager: FragmentManager, val parentIndex: Int) :
FragmentStatePagerAdapter(manager) {
override fun getItem(position: Int): Fragment = ItemListFragment()
override fun getPageTitle(position: Int): CharSequence = "${ position + 1 }"
override fun getCount(): Int = 3
}
And a basic implementation of RecyclerView.Adapter generated by Android Studio
MyItemRecyclerViewAdapter.kt
class MyItemRecyclerViewAdapter(val values: List<DummyItem>) :
RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {
...
}
fragment_item_list.xml
<android.support.v7.widget.RecyclerView 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/list"
android:name="com.example.test.ItemListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"
tools:context="com.example.test.ItemListFragment"
tools:listitem="#layout/fragment_item" />
You should use childFragmentManager instead of activity.supportFragmentManager inside SwipeFragment.
view.pager.adapter = DummyPagerAdapter(childFragmentManager, index)
The difference between getSupportFragmentManager() and getChildFragmentManager() is discussed here.
Basically, the difference is that Fragment's now have their own internal FragmentManager that can handle Fragments. The child FragmentManager is the one that handles Fragments contained within only the Fragment that it was added to. The other FragmentManager is contained within the entire Activity.
In my case childFragmentManager didn't help. I think we can use a pattern "Observer" to update existing fragments of ViewPager with new data when return from another fragment.

Categories

Resources