How to set maximum expanded height in android support design bottom sheet?
The question is an extension to the above question, i want to set the max expanded height of the sheet but dynamically according to the screen size.
I have tried setting new layout params to the view implementing bottomsheet behaviour but it does nothing good.
Please use this and chill :)
const val BOTTOMSHEET_HEIGHT_TO_SCREEN_HEIGHT_RATIO = 0.80 //change according to your requirement
override onCreateDialog() in your bottomsheetFragment
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
dialog.setOnShowListener {
dialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
?.apply {
val maxDesiredHeight =
(resources.displayMetrics.heightPixels * BOTTOMSHEET_HEIGHT_TO_SCREEN_HEIGHT_RATIO).toInt()
if (this.height > maxDesiredHeight) {
val bottomSheetLayoutParams = this.layoutParams
bottomSheetLayoutParams.height = maxDesiredHeight
this.layoutParams = bottomSheetLayoutParams
}
BottomSheetBehavior.from(this)?.apply {
this.state = BottomSheetBehavior.STATE_EXPANDED
this.skipCollapsed = true
}
}
}
return dialog
}
2021
I'm late but someone will need
Kotlin extenxion:
fun View.setupFullHeight(maxHeight: Double = 0.3) {
val displayMetrics = context?.resources?.displayMetrics
val height = displayMetrics?.heightPixels
val maximalHeight = (height?.times(maxHeight))?.toInt()
val layoutParams = this.layoutParams
maximalHeight?.let {
layoutParams.height = it
}
this.layoutParams = layoutParams
}
How to use:
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return object : BottomSheetDialog(requireContext(), R.style.DialogRoundedCornerStyle) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dialog?.setOnShowListener {
val bottomSheetDialog = it as BottomSheetDialog
val parentLayout =
bottomSheetDialog.findViewById<View>(R.id.design_bottom_sheet)
parentLayout?.let { view ->
val behavior = BottomSheetBehavior.from(view)
view.setupFullHeight()
behavior.apply {
state = BottomSheetBehavior.STATE_EXPANDED
isDraggable = false
isCancelable = false
}
}
}
}
override fun onBackPressed() {
super.onBackPressed()
dialog?.dismiss()
}
}
}
The simplest solution is to set the maxHeight property of the bottom sheet like this.
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
bottomSheet.setMaxHeight((int) (displayMetrics.heightPixels * 0.65));
Finally found it,
This question troubled me a lot with no solution reported anywhere, and the answer lies in the behavior itself.
The minimum offset is the max value upto which the bottomsheet should move and we set the lower cap of the value to our desired height upto which we want the bottomsheet to move.
You can expose a function to set the value or do it direclty in our behavior.
To dynamically set the max expanded height for bottomsheet we need to increase the minimum offset value from 0 to our desired value in BottomSheetBehavior class, let me show the code.
Happy coding!!
// The minimum offset value upto which your bottomsheet to move
private int mMinOffset;
/**
* Called when the parent CoordinatorLayout is about the layout the given child view.
*/
#Override
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
int dynamicHeight = Utils.dpToPx(parent.getContext(), **your_value_in_dp**);
mMinOffset = Math.max(dynamicHeight, mParentHeight - child.getHeight());
mMaxOffset = Math.max(mParentHeight - mPeekHeight, mMinOffset);
mAnchorOffset = Math.min(mParentHeight - mAnchorHeight, mMaxOffset);
if (mState == STATE_EXPANDED) {
ViewCompat.offsetTopAndBottom(child, mMinOffset);
anchorViews(mMinOffset);
}
}
Related
I want to resize the width of My button from Kotlin code.
I tried this
private var viewPagerPageChangeListener: ViewPager.OnPageChangeListener = object : ViewPager.OnPageChangeListener {
override fun onPageSelected(position: Int) {
addBottomDots(position)
if (position == layouts!!.size - 1) {
btnNext!!.text = getString(R.string.start)
btnNext!!.layoutParams = RelativeLayout.LayoutParams(315, 44)
btnSkip!!.visibility = View.GONE
} else {
btnNext!!.text = getString(R.string.next)
btnSkip!!.visibility = View.VISIBLE
}
}
I am using this in the View Pager.
After running my application, button is disappeared from the screen.
You can use View's scaleX and scaleY if you just want to simply stretch the view. Getting the actual width and height is a bit complicated as you have to take into account listening to a ViewTreeObserver
I have a problem, because I need to have width of my TextView. I have already width of my Layout, but I need to have a width of specific element too. In this case TextView. I'm trying to get it, but I think that addOnLayoutChangeListener is going on another scope or sth because when I try to assign width to var textWidth I can't do this and variable return 0, but in println I can see that there is a value which I need. How can I get this value?
var textWidth = 0
textViewOne.addOnLayoutChangeListener {
v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom
-> textWidth = right-left
println("${right-left}") <-- this return 389
}
println("${textWidth}") <-- this return 0
Any tips how to do take width of TextView?
I believe the views dimensions are not evaluated at this stage so you don’t have a choice but to wait until the layout is fully rendered.
Assuming you are not measuring a single view, I’d recommend attaching OnGlobalLayoutListener to the root view:
rootView.viewTreeObserver.addOnGlobalLayoutListener {
// Do your thing
}
And if you want the code to be executed only once:
rootView.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
rootView.viewTreeObserver.removeOnGlobalLayoutListener(this)
// Do your thing
}
})
I just solved problem if anyone need solution this workes for me:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val displayMetrics: DisplayMetrics = applicationContext.resources.displayMetrics
val pxWidth = displayMetrics.widthPixels
val baseLayout = findViewById<LinearLayout>(R.id.baseLayout)
baseLayout.doOnLayout {
val textViewOne = findViewById<TextView>(R.id.textVieOne)
val oneWidth = textViewOne.width
val textViewTwo = findViewById<TextView>(R.id.textViewTwo)
val twoWidth = textViewTwo.width
val textViewThree = findViewById<TextView>(R.id.textViewThree)
val threeWidth = textViewThree.width
val sumOfChildWidths = oneWidth + twoWidth + threeWidth
if(pxWidth <= sumOfChildWidths){
textViewThree.isVisible = false
}
}
}
I have a listView filled with multi-line TextViews. Each TextView has a different amount of text. After pressing a button, the user is taken to another Activity where they can change the font and the font size. Upon reEntry into the Fragment, if these settings have changed, the listView is reset and the measurements of the TextViews are changed.
I need to know the measured height of the first TextView in view after these settings have changed. For some reason, the measured height is different at first after it is measured. Once I manually scroll the list, the real height measurement is recorded.
Log output:
After measured: tv height = 2036
After measured: tv height = 2036
After scroll: tv height = 7950
Minimal Code:
class FragmentRead : Fragment() {
private var firstVisiblePos = 0
lateinit var adapterRead: AdapterRead
lateinit var lvTextList: ListView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
lvTextList = view.findViewById(R.id.read_listview)
setListView(lvTextList)
lvTextList.setOnScrollListener(object : AbsListView.OnScrollListener {
var offset = 0
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
offset = if(lvTextList.getChildAt(0) == null) 0 else lvTextList.getChildAt(0).top - lvTextList.paddingTop
println("After scroll: tv height = ${lvTextList[0].height}")
}
}
override fun onScroll(view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
firstVisiblePos = firstVisibleItem
}
})
}
/*=======================================================================================================*/
fun setListView(lv: ListView) {
adapterRead = AdapterRead(Data.getTextList(), context!!)
lv.apply {this.adapter = adapterRead}
}
/*=======================================================================================================*/
inline fun <T : View> T.afterMeasured(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if(measuredWidth > 0 && measuredHeight > 0) {
println("After measured: tv height = ${lvTextList[0].height}")
viewTreeObserver.removeOnGlobalLayoutListener(this)
f()
}
}
})
}
/*=======================================================================================================*/
override fun onStart() {
if(Settings.settingsChanged) {
setListView(lvTextList)
lvTextList.afterMeasured {
println("After measured: tv height = ${lvTextList[0].height}")
}
}
}
}
What I have tried:
I have tried setting a TextView with the text and layoutParams and reading the height as explained here (Getting height of text view before rendering to layout) but the results are the same. The measured height is much less than after I scroll the list.
I have also tried to programatically scroll the list using lvTextList.scrollBy(0,1) in order to trigger the scroll listener or whatever else is triggered when the correct height is read.
EDIT: I put a delay in after coming back to the Fragment:
Handler().postDelayed({
println("tv height after delay = ${lvScriptureList[0].height}")}, 1000)
And this reports the correct height. So my guess is that the OnGlobalLayoutListener is being called to early. Any way to fix this?
Here is my solution. The reason I need to know the height of the TextView is because after the user changes settings (e.g. font, font size, line spacing) the size of the TextView changes. In order to return to the same spot the TextView was in previously, I need to know the height of the newly measured TextView. Then I can go to the same spot (or very close) based on the position previously and recalculating it based on the new height.
So after the settings are changed and the Fragment is loaded back up:
override fun onStart(){
if(Settings.settingsChanged) {
setListView(lvTextList)
lvTextList.afterMeasured {
lvTextList.post { lvTextList.setSelectionFromTop(readPos, 0) }
Handler().postDelayed({
val newOffset = getNewOffset() // Recalculates the new offset based on the last offset and the new TextView height
lvTextList.post { lvTextList.setSelectionFromTop(readPos, newOffset) }
}, 500)
}
}
}
For some reason I had to scroll to a position first before scheduling the delay so I simply just scrolled to the beginning of the TextView.
The 500ms is goofy and is just an estimate but it works. It actually works with a value of 100ms on my phone but I want to ensure a better chance of success across devices.
How do I make a bottomSheet take up the full height of the screen? Setting the peek height has no effect.
Any help would be appreciated.
bottomSheetDialogFragment.getDialog().setOnShowListener((dialog) ->
{
final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog)dialog;
final FrameLayout bottomSheet = bottomSheetDialog.findViewById(R.id.design_bottom_sheet);
if (bottomSheet != null)
{
final BottomSheetBehavior<View> behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
behavior.setPeekHeight(30000); // no effect, bottom sheet does not span entire height of screen
}
});
BottomSheet Layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<!-- rest of layout not shown -->
<FrameLayout
android:id="#+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/bottomSheetHandle"
tools:layout_height="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
You could get the metrics to have access to the height of the screen in pixels and use that reference to set the height of your bottomsheet.
Get Metrics
val metrics = DisplayMetrics()
requireActivity().windowManager?.defaultDisplay?.getMetrics(metrics)
Set state and peekHeight of dialog
bottomSheetDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheetDialog.behavior.peekHeight = metrics.heightPixels
Set height of your view, notice how we are setting this height as the same of the peekHeight of the dialog. I found this the best way when you want a single size for your BottomSheetDialog
bottomSheet.layoutParams.height = metrics.heightPixels
bottomSheet.requestLayout()
at first inside onCreateDialog
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
...
bottomSheetBehavior?.skipCollapsed = true
bottomSheetBehavior?.peekHeight = Resources.getSystem().displayMetrics.heightPixels
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED
return bottomSheet
}
afterward, use this on start method
/**
* to make sheet height full screen
* */
override fun onStart() {
super.onStart()
val metrics = DisplayMetrics()
requireActivity().windowManager?.defaultDisplay?.getMetrics(metrics)
binding.rootContainer.layoutParams.height = metrics.heightPixels
binding.rootContainer.requestLayout()
}
I hope it works fine because it work correctly with me ;)
to accomplish that you can set match_parent to layout params of bottom
sheet like this:
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = BottomSheetDialog(requireContext(), theme)
dialog.setOnShowListener {
val bottomSheetDialog = it as BottomSheetDialog
val parentLayout =
bottomSheetDialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
parentLayout?.let { it ->
val behaviour = BottomSheetBehavior.from(it)
setupFullHeight(it)
behaviour.state = BottomSheetBehavior.STATE_EXPANDED
}
}
return dialog
}
private fun setupFullHeight(bottomSheet: View) {
val layoutParams = bottomSheet.layoutParams
layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT
bottomSheet.layoutParams = layoutParams
}
}
// add this code into your class
#Override
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
View bottomSheet = dialog.findViewById(R.id.design_bottom_sheet);
if (dialog != null) {
bottomSheet.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
}
View view = getView();
view.post(() -> {
View parent = (View) view.getParent();
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) (parent).getLayoutParams();
CoordinatorLayout.Behavior behavior = params.getBehavior();
BottomSheetBehavior bottomSheetBehavior = (BottomSheetBehavior) behavior;
bottomSheetBehavior.setPeekHeight(view.getMeasuredHeight());
((View)bottomSheet.getParent()).setBackgroundColor(Color.TRANSPARENT)
});
}
I am using custom string in NumberPicker. The problem is the text comes in center, while I want it to align to the Left.
I tried extracting the TextView and setting the gravity there, using the following code -
TextView npTextView = (TextView) numberPicker.getChildAt(1);
npTextView.setGravity(Gravity.LEFT);
But it sets the gravity of selected Text only. The text which is in scrolling position, still comes at center.
Is there any way to align the text to left?
PS: I am using API Level 15.
Eight years have passed since the time when the question was asked, so you could have +8 years of Android experience and may be capable of developing the desired number picker yourself :)
Whatever, I've encountered a similar problem and upgraded Android NumberPicker to fulfill my needs. The original NumberPicker is far from perfect, so is mine, but after some tuning it worked for me.
Usage example:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View = LinearLayout(requireContext()).apply {
isBaselineAligned = false
val divider = ColorDrawable(0xFFDBDBDB.toInt()).apply {
// share drawable, don't mind Callback, invalidation etc
setBounds(0, 0, 0, dp2Px(context, 1)) // 1dp dividers
}
val pad = dp2Px(context, 16)
val selectorWheelPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
typeface = ResourcesCompat.getFont(context, R.font.roboto_light)
textSize = sp(22f)
}
val scaleI = TimeInterpolator { cos(3.1415926535897932384f / 2f * it) }
val alphaI = TimeInterpolator { cos(3.1415926535897932384f / 1.5f * it) }
val cmPicker = FlexibleNumberPicker(context).apply {
minValue = 0
maxValue = 1000 // 10 metres is enough, I hope
value = valueMm / 100
setup(divider, selectorWheelPaint, scaleI, alphaI)
setPadding(0, 0, pad, 0)
val cm = resources.getString(R.string.cm)
setFormatter { cm.format(it.toFloat()) }
wrapSelectorWheel = false
setGravity(Gravity.RIGHT)
}
addView(cmPicker, LinearLayout.LayoutParams(0, WRAP_CONTENT, 1f))
addView(FlexibleNumberPicker(context).apply {
minValue = 0
maxValue = 9
value = valueMm % 100
setup(divider, selectorWheelPaint, scaleI, alphaI)
setPadding(pad, 0, 0, 0)
val mm = resources.getString(R.string.mm)
setFormatter { mm.format(it.toFloat()) }
setOnValueChangedListener(cmPicker.fractionListener)
setGravity(Gravity.LEFT)
}, LinearLayout.LayoutParams(0, WRAP_CONTENT, 1f))
}
private fun FlexibleNumberPicker.setup(
divider: ColorDrawable, paint: Paint,
scaleI: TimeInterpolator, alphaI: TimeInterpolator,
) {
setSelectionDivider(divider)
setSelectorWheelPaint(paint)
setWheelItemCount(5)
setDividerOffset(.33f)
setLineHeight(TypedValue.COMPLEX_UNIT_SP, 27f)
solidColor = Color.WHITE
setScaleInterpolator(scaleI)
setAlphaInterpolator(alphaI)
}
val FlexibleNumberPicker.fractionListener: FlexibleNumberPicker.OnValueChangeListener
get() = FlexibleNumberPicker.OnValueChangeListener { picker, oldVal, newVal ->
val min = picker.minValue
val max = picker.maxValue
when {
oldVal == min && newVal == max -> changeValueByOne(false)
oldVal == max && newVal == min -> changeValueByOne(true)
}
}
You can't be sure that 2nd child of NumberPicker is the right TextView because implementation of NumberPicker in different APIs is different. Try this code instead.
for (int i = 0; i < numberPicker.getChildCount(); i++) {
View child = numberPicker.getChildAt(i);
if (child instanceof EditText) {
((EditText) child).setGravity(Gravity.LEFT);
break;
}
}