I've got a problem with one of my views on android, which has quite complex structure.
Basically, what I wan't to achieve is something like this:
Having scrolled view, that has inside some views with wrap content, but last of those views, has gridview. The problem is that without setting exact height of the grid view (and with wrap content)... scroll doesn't allow to scroll to the end of this grid. I don't want to use scroll inside this GridView, I used this widget to simplify data population and use build-in controls and adapters.
My 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">
<ScrollView
android:id="#+id/scrollview_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
// some relevant subviews
</LinearLayout>
// here problem starts
<GridView
android:id="#+id/grid_products"
android:layout_width="match_parent"
android:minHeight="50dp"
android:layout_height="wrap_content" />
// here problem ends
</LinearLayout>
</ScrollView>
I tried to provide custom scrollView with overwritten onMesure, also tried in onResume invalidateScroll and use computeScroll method.
Here is some code for overwritten onMesure:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) getLayoutParams();
params.height = getMeasuredHeight()+10000;
setLayoutParams(params);
invalidate();
requestLayout();
}
But with no luck ... still I have to provide exact value for layout_height, otherwise its not visible and not reachable via scrolling.
I experimented a lot and for now have no idea, what I can check next and to do to make it works.
Thanks in advance for any help,
Regards David
UPDATE WITH WORKING SOLUTION
This works for me https://stackoverflow.com/a/8483078/566817 with custom GridView class, that has this method inside:
public int measureRealHeight(Context context)
{
final int columnsCount = 3;
final int screenWidth = ((Activity)context).getWindowManager().getDefaultDisplay().getWidth();
final double screenDensity = getResources().getDisplayMetrics().density;
final int columnWidth = (int) (screenWidth / columnsCount + screenDensity + 0.5f);
final int verticalSpacing = (int) context.getResources().getDimension(R.dimen.grid_spacing_vertical);
final int rowsCount = getAdapter().getCount() / columnsCount + (getAdapter().getCount() % columnsCount == 0 ? 0 : 1);
return columnWidth * rowsCount + verticalSpacing * (rowsCount - 1);
}
Also need to update LayoutParams in my Fragment in onResume method:
#Override
public void onResume()
{
super.onResume();
int realHeight = gridViewProducts.measureRealHeight(getActivity());
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) gridViewProducts.getLayoutParams();
lp.height = realHeight;
gridViewProducts.setLayoutParams(lp);
gridViewProducts.requestLayout();
}
My grid in XML
<xxx.CustomGridView
android:id="#+id/grid_products"
android:isScrollContainer="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numColumns="3" />
I spend much time on this so far, and have no better solution so far unfortunately (I know its not perfect solution).
I hope it will help someone :)
Related
I want to set Max Height of RecylerView.I am able to set max height using below code.Below code makes height 60% of current screen.
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int a = (displaymetrics.heightPixels * 60) / 100;
recyclerview1.getLayoutParams().height = a;
But now problem is that, if it have no item then also its height is 60%.
So I want to set its height 0 when no item in it.
I want to achieve like something.
if(recyclerview's height > maxHeight)
then set recyclerview's height to maxHeight
else dont change the height of recyclerview.
How can i set it?
Please help me, I am stuck with it
ConstraintLayout offers maximum height for its children.
<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="match_parent">
<ListView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_max="300dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Here is something, you need. Subclass the RecyclerView and override onMeasure as following:
#Override
protected void onMeasure(int widthSpec, int heightSpec) {
heightSpec = MeasureSpec.makeMeasureSpec(Utils.dpsToPixels(240), MeasureSpec.AT_MOST);
super.onMeasure(widthSpec, heightSpec);
}
Make sure, you give proper number of pixels as the first argument in makeMeasureSpec(). I personally needed RecyclerView, that is 240dps at most.
I think this will work at least it worked for me
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/Id_const_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/Id_my_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/margin_top_space"
android:scrollbars="vertical"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="#+id/Id_const_layout"
app:layout_constraintEnd_toEndOf="#+id/Id_const_layout"
app:layout_constraintHeight_max="150dp"
app:layout_constraintHeight_min="30dp"
app:layout_constraintStart_toStartOf="#+id/Id_const_layout"
app:layout_constraintTop_toTopOf="#+id/Id_const_layout"
>
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
You can change the constraintHeight_max and constraintHeight_min according to your needs.
We can optimize #Fattum 's method a little bit.
We can use app:maxHeight to set the maxHeight. One can use dp or px as you like.
public class MaxHeightRecyclerView extends RecyclerView {
private int mMaxHeight;
public MaxHeightRecyclerView(Context context) {
super(context);
}
public MaxHeightRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public MaxHeightRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
private void initialize(Context context, AttributeSet attrs) {
TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
mMaxHeight = arr.getLayoutDimension(R.styleable.MaxHeightScrollView_maxHeight, mMaxHeight);
arr.recycle();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMaxHeight > 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
values/attrs.xml
<declare-styleable name="MaxHeightScrollView">
<attr name="maxHeight" format="dimension" />
</declare-styleable>
After trying way too many complicated suggestions, I finally figured this out through trial and error.
First, wrap the RecyclerView in some sort of layout. In my case, I used a constraint layout, and I even had other elements in that layout above and below the RecyclerView. Set the height of the layout to auto adjust by setting it to 0dp, then set the default layout height constraint as wrap and define a layout max height constraint.
Then, on your RecyclerView, set the height to wrap_content and layout_constrainedHeight to true.
Here is what it should look like:
<android.support.constraint.ConstraintLayout
android:layout_width="600px"
android:layout_height="0dp"
app:layout_constraintHeight_default="wrap"
app:layout_constraintHeight_max="450px">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
app:layout_constraintTop_ToTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
Hope this helps!
write this statements inside if else
and let me know ,
ViewGroup.LayoutParams params=recyclerview.getLayoutParams();
params.height=100;
recyclerview.setLayoutParams(params);
ill add my 2 cents i needed a recycler view with a max height and min height and wrap content between the 2
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="#+id/group_card_recycler_view_holder"
app:layout_constraintHeight_default="wrap"
app:layout_constraintHeight_max="#dimen/group_recycler_view_height"
app:layout_constraintHeight_min="#dimen/card_sentence_height"
android:layout_marginTop="#dimen/activity_horizontal_margin_8dp"
app:layout_constraintTop_toBottomOf="#id/group_name_container"
app:layout_constraintBottom_toTopOf="#id/myButtonLayout">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constrainedHeight="true"
android:id="#+id/group_card_recycler_view"/>
</androidx.constraintlayout.widget.ConstraintLayout>
You can use WRAP_CONTENT in your RecyclerView. It will auto measure your recyclerview according to its content.
You can also calculate current height and set max height. So Recyclerview will use wrap_content attribute value until max height.
public static void getTotalHeightofRecyclerView(RecyclerView recyclerView) {
RecyclerView.Adapter mAdapter = recyclerView.getAdapter();
int totalHeight = 0;
for (int i = 0; i < mAdapter.getItemCount(); i++) {
View mView = recyclerView.findViewHolderForAdapterPosition(i).itemView
mView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
totalHeight += mView.getMeasuredHeight();
}
if (totalHeight > 100) {
ViewGroup.LayoutParams params = recyclerView.getLayoutParams();
params.height = 100;
recyclerView.setLayoutParams(params);
}
}
The simplest way is to use ConstraintLayout and setting height constraint on Recycleview.
<android.support.constraint.ConstraintLayout
android:id="#+id/CL_OUTER_RV_Choose_Categories "
android:layout_width="match_parent"
android:layout_height="200dp">
<android.support.v7.widget.RecyclerView
android:id="#+id/RV_Choose_Categories"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_constrainedHeight="true" >
</android.support.v7.widget.RecyclerView>
</android.support.constraint.ConstraintLayout>
Please note that before Lollipop, the recycleview will not scroll. As a solution, add the below code in the activity's oncreate.
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
ConstraintLayout CL_OUTER_RV_Choose_Categories = findViewById(R.id.CL_OUTER_RV_Choose_Categories);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) CL_OUTER_RV_Choose_Categories.getLayoutParams();
lp.height = LinearLayout.LayoutParams.WRAP_CONTENT;
}
This will ensure that height is fixed only on supported devices, and the complete recycle view is shown on older devices.
Do let me know if there is any better way.
If you have a RecyclerView designed to hold items of equal physical size and you want a no-brainer way to limit its height without extending RecyclerView, try this:
int maxRecyclerViewHeightPixels = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
MAX_RECYCLERVIEW_HEIGHT_DP,
getResources().getDisplayMetrics()
);
MyRecyclerViewAdapter myRecyclerViewAdapter = new MyRecyclerViewAdapter(getContext(), myElementList);
myRecyclerView.setAdapter(myRecyclerViewAdapter);
ViewGroup.LayoutParams params = myRecyclerView.getLayoutParams();
if (myElementList.size() <= MAX_NUMBER_OF_ELEMENTS_TO_DISPLAY_AT_ONE_TIME) {
myRecyclerView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
//LinearLayout must be replaced by whatever layout type encloses your RecyclerView
}
else {
params.height = maxRecyclerViewHeightPixels;
myRecyclerView.setLayoutParams(params);
}
This works both for Linear and Grid RecyclerViews, you just need to play with the numbers a bit to suit your taste. Don't forget to set the height to WRAP_CONTENT after you populate the RecyclerView.
You may also have to set the height again every time the size of myElementList changes, but that's not a big issue.
I just had this same problem, and wanted to share a new, up-to-date answer. This one works on at least Android 9 and Android Studio v. 4.0.
Wrap the RecyclerView in a ConstraintLayout, as some of the answers here have suggested. But it does not appear you can set the maximum height of that layout in XML. Instead, you must set it in code, as follows: Give the ConstraintLayout an ID, and then, in your onCreate or onViewCreated method for the activity or fragment, respectively, in question - for example, to set a max height of 200 dp:
findViewById<ConstraintLayout>(R.id.[YOUR ID]).maxHeight = 200 // activity
view.findViewById<ConstraintLayout>(R.id.[YOUR ID]).maxHeight = 200 // fragment
Annoyingly, ConstraintLayout does expose an XML attribute android:minHeight but no android:maxHeight.
Even more annoyingly, RecyclerView itself apparently comes with an XML attribute named android:maxHeight, but that attribute does not work.
You can just set a specific height to any value in the xml code and set the visibility to gone. Then you set the visibility to Visible in Java, when you want to inflate it. Here is an example;
.xml
android:visibility="gone"
.java
recyclerView.setVisibility(View.VISIBLE);
I have a StaggeredGrid in a RecyclerView and I am trying to dynamically set the image height in the onBindViewHolder. I thought the StaggeredGrid was supposed to handle all this automatically, so I set android:layout_width="match_parent and android:scaleType="fitStart on the ImageView, but there is lots of gaps around the image.
Since the StaggeredGrid isn't living up to it, I'm trying to help it a bit by defining the image height dynamically in the onBindViewHolder. I have the width and height of the image ahead of time, but the cell width of the grid isn't available. I tried holder.cardView.getLayoutParams().width and getMeasuredWidth(), but they both return zero or small numbers. I know there are other places the cell width would be available, but I really need it in the onBindViewHolder event so I can set and adjust the image.
Any ideas on how to achieve this by leveraging the StaggeredGrid? Any advice or experience is appreciated!
In my activity is this:
mLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
In my adapter:
#Override
public void onBindViewHolder(MyAdapter.MyViewHolder holder, int position) {
MyModel item = mData.get(position);
int imageWidth = item.getFeatured_image().getWidth();
int imageHeight = item.getFeatured_image().getHeight();
int cellWidth = 540; // <==== how to find dynamically??!!
ViewGroup.LayoutParams imageLayoutParams = holder.thumbnailImageView.getLayoutParams();
imageLayoutParams.width = cellWidth;
imageLayoutParams.height = imageHeight * cellWidth / imageWidth;
holder.thumbnailImageView.setLayoutParams(imageLayoutParams);
MediaHelper.displayImage(item, holder.thumbnailImageView);
}
In my layout:
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/row_my_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
card_view:cardCornerRadius="2dp">
<ImageView
android:id="#+id/row_my_thumbnail_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:scaleType="fitStart" />
<TextView
android:id="#+id/row_my_title_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#CCFFFFFF"
android:textStyle="bold"
android:padding="5dp" />
</android.support.v7.widget.CardView>
I also tried to add this which does give me the cell width, but the onGlobalLayout event gets triggered way after the image is set and adjusted in the onBindViewHolder event so it's too late.
#Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
final View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_my_post, parent, false);
final MyViewHolder viewHolder = new MyViewHolder(view);
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int viewWidth = view.getWidth();
int viewHeight = view.getHeight();
}
});
}
return viewHolder;
}
Here is the gaps which are random due to the various image sizes. What I would like is for ALL images to be the width of the cell and let the height of the image and cell adjust dynamically:
Try This:- add this in your ImageView--> android:adjustViewBounds="true"
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/row_my_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
card_view:cardCornerRadius="2dp">
<ImageView
android:id="#+id/row_my_thumbnail_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:adjustViewBounds="true"/>
<TextView
android:id="#+id/row_my_title_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#CCFFFFFF"
android:textStyle="bold"
android:padding="5dp" />
</android.support.v7.widget.CardView>
Instead of getting the view height and width from onCreateViewHolder get it from onBindViewHolder as below:
#Override
public void onBindViewHolder(final MyAdapter.MyViewHolder holder, int position)
{
holder.itemView.post(new Runnable()
{
#Override
public void run()
{
MyModel item = mData.get(position);
int imageWidth = item.getFeatured_image().getWidth();
int imageHeight = item.getFeatured_image().getHeight();
int cellWidth = 540; // <==== how to find dynamically??!!
cellWidth = holder.itemView.getWidth();// this will give you width dynamically
ViewGroup.LayoutParams imageLayoutParams = holder.thumbnailImageView.getLayoutParams();
imageLayoutParams.width = cellWidth;
imageLayoutParams.height = imageHeight * cellWidth / imageWidth;
holder.thumbnailImageView.setLayoutParams(imageLayoutParams);
MediaHelper.displayImage(item, holder.thumbnailImageView);
}
});
}
I hope this will help you!!
The cell's width can be found at activity/fragment level and passed to the adapter before rendering time. Use something like this:
this.staggeredGridLayoutManager.getWidth() / this.staggeredGridLayoutManager.getSpanCount()
With this approach, you will be able to compute the cell's height before any rendering and avoid that initial flick introduced by post run approach.
Also, the image's aspect ratio will be preserved and you can reserve the required space before image load.
Be also aware that if any margins/paddings are present you should include them into previous calculation. Something like:
(this.staggeredGridLayoutManager.getWidth() - marginsAndOrPaddings) / this.staggeredGridLayoutManager.getSpanCount()
Update:
Starting with support packages 23.2.0, and at this point of lifecycle, this.staggeredGridLayoutManager.getWidth() returns 0. The total width can be achieved with this.recyclerView.getWidth()
I have a CustomViewPager inside an ObservableScrollView which looks like this:
It seems to measure the fragment but does not measure the height of the fragment which is off the screen. So I can't actually scroll up.
This is the code for the CustomViewPager:
public class CustomViewPager extends ViewPager {
private View view;
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
View tab = getChildAt(getCurrentItem());
int width = getMeasuredWidth();
int tabHeight = tab.getMeasuredHeight();
if (wrapHeight) {
// Keep the current measured width.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}
int fragmentHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, getCurrentItem())).getView());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(tabHeight + fragmentHeight + (int)
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics()), MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public int measureFragment(View view) {
if (view == null)
return 0;
view.measure(0, 0);
return view.getMeasuredHeight();
}
}
NOTE: If I add say + 1000 to heightMeasureSpec in super.onMeasure(widthMeasureSpec, heightMeasureSpec); then I can scroll the size of the fragment as well as any extra space. But obviously this is not a preferred solution.
Here is my XML file:
<FrameLayout 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="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/infoBox"
android:orientation="vertical"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="240dp">
<!-- Code for other views -->
</LinearLayout>
<com.github.ksoichiro.android.observablescrollview.ObservableScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="266dp"
android:orientation="vertical">
<com.ui.customviewpager.CustomViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</com.github.ksoichiro.android.observablescrollview.ObservableScrollView>
What seems to be the issue here? It seems like I am not the only one with this issue.
I implemented some of this design from this link here
ScrollView child should not have margin for the scrollView to display properly.
It seems that the margin is not taken into account when the scrollView measure its child.
Try removing margin of LinearLayout (you can use padding to reproduce the effect).
Why dont you use ObservableScrollView inside the fragment and put the viewpager inside a TouchInterceptionFrameLayout (from the same lib), then set setTouchInterceptionViewGroup of the ObservableScrollView to be the one containing your viewpager and do the logic on the TouchInterceptionListener in your activity to either move it up and down with with animation in order to mimic a smooth scrolling effect.
I hope that is that your are looking for to achieve and hopefully it will help you.
Ps: See the guy example app and source (Link), it is a bit complex but im sure you will find something.
Hello i am not much aware of ObservableScrollView but according to your problem you are not able to find out height of fragment. After searching a lot i am able to find out tutorial to get the height of fragments.
here is the link
http://adanware.blogspot.in/2012/06/android-getting-measuring-fragment.html
Hope, this will help you.
I want my android layout to behave like so:
Suppose SomeLayout is a parent of Widget1, Layout2, and Widget2, which are displayed in that order from top to bottom, like so:
|-------SomeLayout-------|
||--------Widget1-------||
||______________________||
||--------Layout2-------||
||______________________||
||--------Widget2-------||
||______________________||
|________________________|
The 3 children have content that is changing. Dynamically as they change, I always want the one with the widest content to wrap to content. I want SomeLayout to wrap around that widest child, and I want all other children to match that widest one. Even if which one is widest changes.
I have been trying for some time to accomplish this using more methods than I can count. All failures. Does anyone know haw to achieve this effect via the android layout XML? I would be willing to use a Java programmatic solution if that's all that anyone can provide, but I would prefer to do it via just the XML.
Good Question buddy.
After started checking I felt this was a good question..
Here is what you want.. Check it out..
Its just simple one..
Apply some layout width to wrap_content and all direct child's width to match_parent..
see the example below.. you can conform it assigining the background color.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- This is Your Some Layout -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<!-- Widget1 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="2431234vb sset " />
<!-- Your Layout 2 (inner one) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2431234v " />
</LinearLayout>
<!-- Widget2 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="2431234vb sset " />
</LinearLayout>
</LinearLayout>
Hope It will help you..
I hope your SomeLayout extends ViewGroup, so I'd try to override onMeasure method in order to measure all the childs in a proper way.
For example
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int specWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxWidth = 0;
int maxIndex = 0;
for (int i = 0; i < getChildCount(); i++){
View child = getChildAt(i);
final int widthSpec = MeasureSpec.makeMeasureSpec(
specWidth, MeasureSpec.UNSPECIFIED);
final int heightSpec = MeasureSpec.makeMeasureSpec(
0, MeasureSpec.UNSPECIFIED);
child.measure(widthSpec, heightSpec);
int width = child.getMeasuredWidth();
if (width > maxWidth){
maxWidth = width;
maxIndex = i;
}
}
for (int i = 0; i < getChildCount(); i++){
if (i != maxIndex){
View child = getChildAt(i);
final int widthSpec = MeasureSpec.makeMeasureSpec(
maxWidth, MeasureSpec.EXACTLY);
final int heightSpec = MeasureSpec.makeMeasureSpec(
0, MeasureSpec.UNSPECIFIED);
child.measure(widthSpec, heightSpec);
}
}
setMeasuredDimension(someMeasuredWidth, someMeasuredHeight);
}
So you should define the widest child and measure all the others in order to adjust maximum size. After that you should layout all the childs in onLayout method implementation according measured dimensions.
Hope it will help you
What I want is to have a layout with 2 views arranged vertically. Let's call the top view A and the bottom one B. I want the amount of height given to B to be its normal height (i.e. wrap content) except that I don't want it to be given more than half of the available space. A gets what is left.
Another way to word it is that A should always get at least 50% of the available height and B should get at most 50%.
I can't seem to find an easy way to achieve that. I can set both layout heights to 0 and give them equal weights which makes them both 50% always, but if B is smaller than 50% it should be given only what it needs.
The only way I can see to do it is use a custom class for A or B and override onMeasure to constrain the height to 50% of the parent, but it seems there should be an easier way.
Ok, I got it now. If I understood correctly you want to have it like this:
if A > B -> do nothing
if B > A & B > parent layout -> 50% to both of them
if B > A & B < parent layout -> A = parent layout - B
I had to do it all in onWindowFocusChanged because otherwise in onCreate the height of the Views would return 0. I did it with 2 LinearLayouts as child layouts, but you can take what ever you want.
My XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="#+id/parent_lay"
android:orientation="vertical"
android:layout_height="match_parent" >
//Layout A:
<LinearLayout
android:id="#+id/lay_1"
android:layout_width="match_parent"
android:background="#android:color/background_dark"
android:layout_height="10dp" >
</LinearLayout>
//Layout B:
<LinearLayout
android:id="#+id/lay_2"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#123456" >
</LinearLayout>
</LinearLayout>
MainActivity:
public class MainActivity extends Activity {
LinearLayout parent_lay;
LinearLayout lay_1;
LinearLayout lay_2;
int parent_height;
int lay_1_height;
int lay_2_heigth;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
// TODO Auto-generated method stub
super.onWindowFocusChanged(hasFocus);
parent_lay = (LinearLayout) findViewById(R.id.parent_lay);
lay_1 = (LinearLayout) findViewById(R.id.lay_1);
lay_2 = (LinearLayout) findViewById(R.id.lay_2);
lay_1_height = lay_1.getHeight();
lay_2_heigth = lay_2.getHeight();
parent_height = parent_lay.getHeight();
if (lay_2.getHeight() > lay_1.getHeight()
&& lay_2.getHeight() > (parent_lay.getHeight() / 2)) {
lay_1.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, 0, 1));
lay_2.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, 0, 1));
} else if (lay_2.getHeight() < (parent_lay.getHeight() / 2)) {
lay_1.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, (parent_height - lay_2_heigth)));
}
}
}
Example:
If A is 60dp and B is 40dp:
If A is 60dp and B is 400dp:
You must write your own component to achieve this.
For example, if you use LinearLayout here, you can extends a LinearLayout with overdid onMeasure method. You can implement onMeasure like this:
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = getMeasuredWidth();
final int height = getMeasuredHeight();
setMeasuredDimension(width, height / 2);
}
This code is not elegant enough. If you really want to do it well, copy the original onMeasure method from Android source code (http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/1.5_r4/android/widget/LinearLayout.java#LinearLayout.onMeasure%28int%2Cint%29), and in measureVertical(int widthMeasureSpec, int heightMeasureSpec), set mTotalLength = mTotalLength / 2.
For detailed information of onMeasure, visit http://developer.android.com/guide/topics/ui/custom-components.html and http://developer.android.com/reference/android/view/View.html#onMeasure(int, int).
Now the desired effect can be achieved with the ConstraintLayout:
<?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="match_parent">
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/containerFrameLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<android.support.constraint.Guideline
android:id="#+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5"/>
<FrameLayout
android:id="#+id/containerFrameLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/guideline"
app:layout_constraintVertical_bias="1">
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout>
</android.support.constraint.ConstraintLayout>
Create a linear layout with two inner frames, each with .5 weight. Inside those frames, place your views, setting them to wrap_content or match_parent as appropriate.