I have an AppBarLayout with CollapsingToolbarLayout, two views below and ViewPager at the rest of the screen
<Coordinator>
<AppBarLayout>
// Toolbar stuff inside
<CollapsingToolbarLayout app:layout_scrollFlags="scroll|exitUntilCollapsed"/>
// Should collapse by design
<FirstButtonView app:layout_scrollFlags="scroll" />
<TabLayout app:layout_scrollFlags="scroll|exitUntilCollapsed">
</AppBarLayout>
<ViewPager app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</Coordinator>
With this layout it collapsed toolbar as expected, but both views are being pinned to the top
As i see in AppBarLayout class, when it calculates possible scroll, it iterates through child views until meet flag SCROLL_FLAG_EXIT_UNTIL_COLLAPSED=2, then breaks the loop
public final int getTotalScrollRange() {
if (this.totalScrollRange != -1) {
return this.totalScrollRange;
} else {
int range = 0;
int i = 0;
for(int z = this.getChildCount(); i < z; ++i) {
View child = this.getChildAt(i);
AppBarLayout.LayoutParams lp = (AppBarLayout.LayoutParams)child.getLayoutParams();
int childHeight = child.getMeasuredHeight();
int flags = lp.scrollFlags;
if ((flags & 1) == 0) {
break;
}
range += childHeight + lp.topMargin + lp.bottomMargin;
if ((flags & 2) != 0) {
range -= ViewCompat.getMinimumHeight(child);
break;
}
}
return this.totalScrollRange = Math.max(0, range - this.getTopInset());
}
}
Is it possible somehow to solve it?
Target design
Based on Documentation on using "SCROLL_FLAG_SCROLL", "If any sibling views before this one do not have this flag, then this value has no effect."
Hence the same flag has to be used with other siblings.
Related
I'm trying to implement the tablayout, I would like to set width of the tab depending on text content in individual tabs, right now it is set equally, which results on small text, tab width feels higher.
Try this:
public void wrapTabIndicatorToTitle(TabLayout tabLayout, int externalMargin, int internalMargin) {
View tabStrip = tabLayout.getChildAt(0);
if (tabStrip instanceof ViewGroup) {
ViewGroup tabStripGroup = (ViewGroup) tabStrip;
int childCount = ((ViewGroup) tabStrip).getChildCount();
for (int i = 0; i < childCount; i++) {
View tabView = tabStripGroup.getChildAt(i);
//set minimum width to 0 for instead for small texts, indicator is not wrapped as expected
tabView.setMinimumWidth(0);
// set padding to 0 for wrapping indicator as title
tabView.setPadding(0, tabView.getPaddingTop(), 0, tabView.getPaddingBottom());
// setting custom margin between tabs
if (tabView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) tabView.getLayoutParams();
if (i == 0) {
// left
settingMargin(layoutParams, externalMargin, internalMargin);
} else if (i == childCount - 1) {
// right
settingMargin(layoutParams, internalMargin, externalMargin);
} else {
// internal
settingMargin(layoutParams, internalMargin, internalMargin);
}
}
}
tabLayout.requestLayout();
}
}
private void settingMargin(ViewGroup.MarginLayoutParams layoutParams, int start, int end) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
layoutParams.setMarginStart(start);
layoutParams.setMarginEnd(end);
} else {
layoutParams.leftMargin = start;
layoutParams.rightMargin = end;
}
}
After setting the view pager in java file add :
wrapTabIndicatorToTitle(tabLayout,60,60);
Try this! It will reduce your efforts.
<android.support.design.widget.TabLayout
android:id="#+id/tblHome"
android:layout_width="match_parent"
android:layout_height="48dp"
app:tabMode="scrollable">
Put this below XML code for TabLayout
<android.support.design.widget.TabLayout
android:id="#+id/tab_Layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
app:tabGravity="fill"
app:tabMode="scrollable"
/>
Is there any way I can adjust listview's height as of it's content height in xamarin.forms? I could successfully do it for ios but for android, I applied a solution that leads to slow layout rendering.
code
public class CustomListViewRenderer : ListViewRenderer
{
...
protected async override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
if(Element == null || ((CustomListView)Element).IsScrollable)
{
return;
}
var view = (CustomListView)Element;
if(!view.IsScrollable)
{
var mAdapter = nativeList.Adapter;
int totalHeight = 0;
int listWidth = nativeList.MeasuredWidth;
int listHeight = nativeList.MeasuredHeight;
if(totalCount == nativeList.Count)
{
//return;
}
for (int i = 0; i < mAdapter.Count; i++)
{
global::Android.Views.View mView = mAdapter.GetView(i, null, nativeList);
mView.Measure(MeasureSpec.MakeMeasureSpec(listWidth, MeasureSpecMode.Exactly),
MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified));
totalHeight += (int)(mView.MeasuredHeight / Resources.DisplayMetrics.Density);
totalCount = i + 1;
}
ViewGroup.LayoutParams param = nativeList.LayoutParameters;
param.Height = totalHeight
+ (nativeList.DividerHeight * (mAdapter.Count - 1));
view.HeightRequest = param.Height;
}
}
}
This however does not always generate exact height for listview, sometimes leaving space at bottom. Moreover it creates a great delay in laying out the page where list view has been used.
Can anyone please help me with this?
Is there any way I can adjust listview's height as of it's content height in xamarin.forms?
You could using HasUnevenRows property to implement this feature :
HasUnevenRows – true/false value, rows have varying heights if set to true. Defaults to false.
Row heights don't have to be manually set once HasUnevenRows has been set to true, because the heights will be automatically calculated by Xamarin.Forms.
C# :
RowHeightDemoListView.HasUnevenRows = true;
XAML :
<ListView x:Name="RowHeightDemoListView" HasUnevenRows="true" />
Essentially, I need an "options" menu that can be accessed by swiping the screen left to right (or clicking on the options button in the left top corner of the screen). I also need it to cover the screen, but not fully replace it (it needs to be semi-opaque so that the previous menu is visible beneath it). What I have so far (I'm working on someone else's code, still haven't dechiphered it all, sorry for the lack of information):
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- As the main content view, the view below consumes the entire
space available using match_parent in both dimensions. -->
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- android:layout_gravity="start" tells DrawerLayout to treat
this as a sliding drawer on the left side for left-to-right
languages and on the right side for right-to-left languages.
The drawer is given a fixed width in dp and extends the full height of
the container. A solid background is used for contrast
with the content view. -->
<ListView
android:id="#+id/left_drawer"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#color/blue"
/>
</android.support.v4.widget.DrawerLayout>
I have some functions to fill in the layout with clickable options, however, I can't make the options go full screen. I swipe left to right, and it only goes about 75% of the way. How can I make it a full-screen options panel?
(I can't make it a new Activity, it needs to overlap with the previous one)
I have the opacity handled and the options buttons, I just can't make this go all the way to the right side of the screen. :D
This works on all android versions:
View mSlidingView = findViewById(R.id.slider);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) mSlidingView.getLayoutParams();
params.width = metrics.widthPixels;
mSlidingView.setLayoutParams(params);
Put this In your onCreate.
The best way to do this is to create a custom DrawerLayout. I found this solution to work perfectly.
Link to custom Full-Screen drawer layout implementation
public class FullDrawerLayout extends DrawerLayout {
private static final int MIN_DRAWER_MARGIN = 0; // dp
public FullDrawerLayout(Context context) {
super(context);
}
public FullDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FullDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
throw new IllegalArgumentException(
"DrawerLayout must be measured with MeasureSpec.EXACTLY.");
}
setMeasuredDimension(widthSize, heightSize);
// Gravity value for each drawer we've seen. Only one of each permitted.
int foundDrawers = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (isContentView(child)) {
// Content views get measured at exactly the layout's size.
final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
child.measure(contentWidthSpec, contentHeightSpec);
} else if (isDrawerView(child)) {
final int childGravity =
getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
if ((foundDrawers & childGravity) != 0) {
throw new IllegalStateException("Child drawer has absolute gravity " +
gravityToString(childGravity) + " but this already has a " +
"drawer view along that edge");
}
final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
MIN_DRAWER_MARGIN + lp.leftMargin + lp.rightMargin,
lp.width);
final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
lp.topMargin + lp.bottomMargin,
lp.height);
child.measure(drawerWidthSpec, drawerHeightSpec);
} else {
throw new IllegalStateException("Child " + child + " at index " + i +
" does not have a valid layout_gravity - must be Gravity.LEFT, " +
"Gravity.RIGHT or Gravity.NO_GRAVITY");
}
}
}
boolean isContentView(View child) {
return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
}
boolean isDrawerView(View child) {
final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
final int absGravity = Gravity.getAbsoluteGravity(gravity,
child.getLayoutDirection());
return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
}
int getDrawerViewGravity(View drawerView) {
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
return Gravity.getAbsoluteGravity(gravity, drawerView.getLayoutDirection());
}
static String gravityToString(int gravity) {
if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
return "LEFT";
}
if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
return "RIGHT";
}
return Integer.toHexString(gravity);
}
}
<include
android:id="#+id/left_drawer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
layout="#layout/drawer"
android:layout_marginLeft="-64dp"/>
It's too late to answer, but for future reads of this question, I am giving my 2c tip: view which holds drawer should have end and right margins set to -64dp, because this value is aparently fixed in the code for rendering drawer.
So, in this case and since android:layout_gravity="start", ListView should have two additional parameters set as follows:
<ListView
...
android:layout_marginEnd="-65dp"
android:layout_marginRight="-65dp" />
On the other hand, if android:layout_gravity="end", then android:layout_marginStart and android:layout_marginLeft should be set to -65dp.
FrameLayout frameLayout = findViewById(R.id.frame_menu);
DrawerLayout.LayoutParams fparams = (DrawerLayout.LayoutParams) frameLayout.getLayoutParams();
DisplayMetrics displaymetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay()
.getMetrics(displaymetrics);
int w = displaymetrics.widthPixels;
fparams.width = w;
frameLayout.setLayoutParams(fparams);
frameLayout.requestLayout();
I have an activity that contains a FrameLayout that will have a dynamic length according to the content ..
The FrameLayout is contained within a ScrollView, as you can see from the picture ..
Image
calculate the height of the frameLayout, using the following function, it moves the position of the scroll and does not start from the point (0,0), but the starting point of the FrameLayout to see the controls above it is necessary to scroll the view.
public void calcolaAltezza() {
int totalHeight = 0;
for (int i = 0; i < sectionAdapter.getCount(); i++) {
View listItem = sectionAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += divisoreLista + listItem.getMeasuredHeight();
}
listaTour.getLayoutParams().height = totalHeight;
sv.scrollTo(0,0); //DON'T WORK
}
Why does this happen?
I have three ListView widgets in the same LinearLayout. Something like this (I'm omitting XML elements that are not relevant in this example):
<LinearLayout>
<ListView
android:layout_width="fill_parent"
android:layout_height="360dp"/>
<ListView
android:layout_width="fill_parent"
android:layout_height="360dp"/>
<ListView
android:layout_width="fill_parent"
android:layout_height="360dp"/>
</LinearLayout>
This forces the list to have a height of 360 dip. Of course, that will be its height even if there are few list items. So, my question is how can make the lists have an automatic height? What I want is that the ListView height takes the exact size of the sum of all its list items.
I've implemented it this way (code is work in progress so it's more a idea source than solution):
package com.customcontrols;
public class NoScrollListView extends ListView
{
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0) );
// here I assume that height's being calculated for one-child only, seen it in ListView's source which is actually a bad idea
int childHeight = getMeasuredHeight() - (getListPaddingTop() + getListPaddingBottom() + getVerticalFadingEdgeLength() * 2);
int fullHeight = getListPaddingTop() + getListPaddingBottom() + childHeight*(getCount());
setMeasuredDimension(getMeasuredWidth(), fullHeight);
}
}
the calculation's not perfect, but it's close and works so far.
after that you just create a layout like this:
ScrollView
com.customcontrol.NoScrollListView
com.customcontrol.NoScrollListView
com.customcontrol.NoScrollListView
/ScrollView
The scrollView's crucial since you can easily run out of screen bounds.
PS. The calculation's rectum-driven since most of the calculation methods in ListView&Co are package private which is quite a strange choice for publicly inheritable classes for UI.
Instead of editing my previous answer, here's my actual version (after finally the case became the issue and I had to fix it)
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0));
int childHeight = getMeasuredHeight() - (getListPaddingTop() + getListPaddingBottom() + getVerticalFadingEdgeLength() * 2);
// on a first run let's have a space for at least one child so it'll trigger remeasurement
int fullHeight = getListPaddingTop() + getListPaddingBottom() + childHeight*(getCount());
int newChildHeight = 0;
for (int x = 0; x<getChildCount(); x++ ){
View childAt = getChildAt(x);
if (childAt != null) {
int height = childAt.getHeight();
newChildHeight += height;
}
}
//on a second run with actual items - use proper size
if (newChildHeight != 0)
fullHeight = getListPaddingTop() + getListPaddingBottom() + newChildHeight;
setMeasuredDimension(getMeasuredWidth(), fullHeight);
}
The trickiest part is that the onMeasure gets called twice, first for approx layout and then after items added for a precise one and both have to return sane results.
i found a solution on set ListView Height Based On Children, this work
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
try this... list view real height
list view xml coding
<ListView
android:id="#+id/my_listview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
list view java coding
ListView lView = (ListView)findViewById(R.id.my_listview);
lView.setAdapter(adapter);
setListViewHeightBasedOnChildren(lView);
setListViewHeightBasedOnChildren method
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
You can try iterating over the ListView's rows (see methods on ViewGroup), find their heights, sum them, and change your ListView height.
That being said, I fully expect this to be awful:
You need to take into account the screen size, so your lists do not go past the bounds of the screen
You need to take into account orientation changes
You need to take into account any modifications to your data (e.g., deleting rows may require your list to shrink)
Etc.
If your goal really is to have a single list, but you have three sources of data, merge the data and put it into a single list. My MergeAdapter can help here.