I need to center elements in each row so they will be like in this mockup.
I've been searching if there is any layout that works that way without look.
items are centered in their rows.
This is how it looks now in my code.
Make recyclerview width to wrap_content and its container's layout_gravity to center_horizontal
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycrer_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingLeft="16dp"
android:paddingRight="16dp" />
</LinearLayout>
Take LinearLayout in your RecyclerView's item row layout then give android:layout_gravity="center" to LinearLayout.
For each row of images you have to take different LinearLayout.
Here is example code:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal">
<ImageView
android:id="#+id/image1"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:src="#drawable/image1" />
<ImageView
android:id="#+id/image2"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:src="#drawable/image2" />
<ImageView
android:id="#+id/image3"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:src="#drawable/image3" />
</LinearLayout>
Achieved with:
recyclerView.adapter = MyAdapter()
val layoutManager = FlexboxLayoutManager(context).apply {
justifyContent = JustifyContent.CENTER
alignItems = AlignItems.CENTER
flexDirection = FlexDirection.ROW
flexWrap = FlexWrap.WRAP
}
recyclerView.layoutManager = layoutManager
You need to add FlexboxLayout to your gradle:
implementation 'com.google.android.flexbox:flexbox:3.0.0'
I am assuming that you are using a LinearLayoutManager with a RecyclerView for a ListView-style effect. In that case, use a horizontal LinearLayout for each row, with android:gravity="center" to center its contents.
Use FlexboxLayout from com.google.android:flexbox library. Note the flexwrap and justifyContent property values. Then, set layout_wrapBefore = true on the view that you want to wrap the line of items before.
<com.google.android.flexbox.FlexboxLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:justifyContent="center">
<View ... />
<View app:layout_wrapBefore="true" ... />
<View ... />
</com.google.android.flexbox.FlexboxLayout>
Use the gridLayoutManager = new GridLayoutManager(context,6) and override setSpanSizeLookup.
Example:
int totalSize=list.size();
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
int span;
span = totalSize % 3;
if (totalSize < 3) {
return 6;
} else if (span == 0 || (position <= ((totalSize - 1) - span))) {
return 2;
} else if (span == 1) {
return 6;
} else {
return 3;
}
}
});
This will work if you want to display 3 items in a row and remaining in the center of the grid.
Change the grid layout manager span counts accordingly your requirement.
you can use the new layoutManager from google FlexLayoutManager
it will gives you a lot of control and flexibility over the recyclerView items
you can add some layouts dynamically, for example:
adapter object can be a horizontal LinearLayout
1- create a LinearLayout in java code and customize it (gravity,...)
2- add some icons to linearLayout
3- add linearLayout to your adapter
4- repeat 1,2,3
// : declare new horizontal linearLayout
ImageView myIcons[nomOfIcons];
// : add all icons to myIcons
for(int i=1; i<=nomOfIcons; i++){
linearLayout.addView(myIcons[i - 1]);
if(i%numOfIconsInOneHorizontalLinearLayout==0) {
results.add(linearLayout); // add linearLayout to adapter dataSet
// : declare new horizontal linearLayout
}
}
if(n%numOfIconsInOneHorizontalLinearLayout!=0) // add last linearLayout if not added in the loop
results.add(linearLayout);
mAdapter.notifyDataSetChanged(); // update adapter
Currently, I think we need to write our own custom view for it because Android has not supported this feature as we expected yet.
It is the Sample I made in case someone needs it:
https://github.com/mttdat/utils
(Try the CenterGridActivity.kt by adjusting Manifest file to start this activity as default)
Trust me, this gonna work.
In your main xml file where you've put the recycler view, copy my code.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="#dimen/_50sdp"
android:gravity="center">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="#dimen/_50sdp"/>
</LinearLayout>
Related
I have the following layout:
Each of the Pokemon Team rows has a fixed size of 100dp :
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pokemonTeambuilderContainer"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="#+id/pokemonTeambuilderTitleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="#string/untitled"
android:textSize="16sp"
android:textStyle="bold" />
<LinearLayout
android:id="#+id/pokemonTeambuilderSpritesLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal" />
</LinearLayout>
</androidx.cardview.widget.CardView>
The Pokemon inside each row are image view created in this adapter :
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
PokemonTeam pokemonTeam = mData.get(position);
for (Pokemon pokemon : pokemonTeam.getPokemonList()) {
CircularImageView ivPokemonSprite = new CircularImageView(mContext);
int color = PokemonUtils.getDominantColorFromPokemon(pokemon.get_id(), mContext);
ivPokemonSprite.setBorderColor(color);
ivPokemonSprite.setBorderWidth(4);
ivPokemonSprite.setCircleColor(PokemonUtils.lighterColor(color, DARK_FACTOR));
Picasso.get().load(PokemonUtils.getPokemonSugimoriImageById(pokemon.get_id(), mContext)).fit().centerCrop().into(ivPokemonSprite);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT, 1);
layoutParams.setMargins(3, 10, 3, 10);
ivPokemonSprite.setLayoutParams(layoutParams);
holder.teamSpritesLinearLayout.addView(ivPokemonSprite);
}
String pokemonTeamName = pokemonTeam.getName();
if (pokemonTeamName != null && !pokemonTeamName.isEmpty()) {
holder.teamTitleTextView.setText(pokemonTeam.getName());
}
}
My problem is when I rotate the device :
I would like to have at least 200dp of height in order to make each row bigger (otherwise I think that in devices like Tablet it will look very small), like this :
you can have alternative layouts for different devices, I think that's the most reliable way https://developer.android.com/guide/practices/screens_support
You can also check the orientation and set up the height accordingly https://stackoverflow.com/a/2799001/12506486
I have a bunch of tiles that I want to distribute horizontally across the screen. They'll be too wide show up on a single row for most devices, so I'd like to use a ConstraintLayout with a Flow helper. I can get that to work.
But I'd like to make sure each tile is the same width. Ideally each tile would be as wide as the required width of the widest tile. What's the right way to make sure that each element in my Flow gets the same width?
(I tried creating a chain and using layout_constraintHorizontal_weight but that just squeezes every tile into the same row and prevents the Flow from flowing.)
You can achieve the same width for the children just using Flow like this:
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<View
android:id="#+id/view1"
android:layout_width="0dp"
android:layout_height="100dp"
android:background="#333" />
<View
android:id="#+id/view2"
android:layout_width="0dp"
android:layout_height="100dp"
android:background="#333" />
<View
android:id="#+id/view3"
android:layout_width="0dp"
android:layout_height="100dp"
android:background="#333" />
<View
android:id="#+id/view4"
android:layout_width="0dp"
android:layout_height="100dp"
android:background="#333" />
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:constraint_referenced_ids="view1,view2,view3,view4"
app:flow_horizontalGap="8dp"
app:flow_maxElementsWrap="2"
app:flow_verticalGap="8dp"
app:flow_wrapMode="chain"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Preview:
Note: One downside in this solution is, if you have an odd number of elements, then the last element will fill the full width. You can programatically set the last element width at runtime.
I ended up adding a ViewTreeObserver that creates a ConstraintSet that sets the size of the children before layout occurs. It isn't pretty, but it does roughly what I want:
final ConstraintLayout statContainer = findViewById(R.id.stat_container);
statContainer.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
try {
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(statContainer);
// Determine a minimum width
int width = 0;
for (int i = 0; i < statContainer.getChildCount(); i++) {
View child = statContainer.getChildAt(i);
if (child instanceof Flow) {
continue;
}
int candidateWidth = child.getMeasuredWidth();
width = Math.max(width, candidateWidth);
}
// Don't allow any child to get too big
int max = statContainer.getWidth() / 3;
width = Math.min(width, max);
// Set that width
for (int i = 0; i < statContainer.getChildCount(); i++) {
View child = statContainer.getChildAt(i);
if (child instanceof Flow) {
continue;
}
constraintSet.constrainWidth(child.getId(), width);
}
constraintSet.applyTo(statContainer);
statContainer.invalidate();
} finally {
statContainer.getViewTreeObserver().removeOnPreDrawListener(this);
}
return false;
}
});
I have a slight problem getting my layout_weight to work. I am creating a custom bottomBar. But i cant make it to stretch as i want it to.
Bottom Bar View
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/bottom_bar_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/bottom_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="4"
android:orientation="horizontal"/>
</RelativeLayout>
This is the big container i am adding my buttons (items) to.
Bottom Bar Item
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<android.support.v7.widget.AppCompatImageView
android:id="#+id/bottom_bar_item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#android:drawable/arrow_up_float"/>
<TextView
android:id="#+id/bottom_bar_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TEXT"/>
</LinearLayout>
This is my items that i add dynamically to the view. But somehow the view is not stretched properly.
But when i hardcode it in. It works. So could it be that layout weight does not work dynamically?
How i add the views (items)
private void updateItems(final List<BottomBarTab> bottomBarTabs) {
if (bottomBarTabs.size() < TABS_COUNT) {
throw new RuntimeException("Not enough buttons for bottomBar");
}
for (int i = 0; i < TABS_COUNT; i++) {
bottomBarTabs.get(i).setOnClickListener(this);
bottomBarTabs.get(i).prepareLayout();
container.addView(bottomBarTabs.get(i));
}
}
Screenshot
LayoutWeight is given to inner components of layout only not on parent Linearlayout.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:weightSum="2">
<android.support.v7.widget.AppCompatImageView
android:id="#+id/bottom_bar_item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="#android:drawable/arrow_up_float"/>
<TextView
android:id="#+id/bottom_bar_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TEXT"/>
</LinearLayout>
public void prepareLayout() {
View view = inflate(getContext(), R.layout.bottom_bar_item,this);
LinearLayout.LayoutParams params =
new LayoutParams(0,LayoutParams.MATCH_PARENT,LAYOUT_WEIGHT);
view.setLayoutParams(params);
if(backgroundColor != null) {
view.setBackgroundColor(backgroundColor);
}
TextView titleText = (TextView) view.findViewById(R.id.bottom_bar_item_text);
titleText.setText(title);
AppCompatImageView imageView = (AppCompatImageView) view.findViewById(R.id.bottom_bar_item_icon);
imageView.setImageResource(iconResId);
}
I changed in my prepareLayout function and put new layoutParams. Because somehow after inflation. The view ignores the weight that was set to it. So i had to force it by code. Maybe this is a android bug?
I am adding pictures to a linearlayout programmatically. This linearlayout is surrounded by a horizontalscrollview and part of a item layout for a listview. When I have the pictures inline with the other view items, they are spaced next to eachother correctly:
However if I move the horizontalscrollview/linearlayout under the other view items, I get some weird spacing that android seems to do automatically:
So far I have tried relativelayouts, embedded linearlayouts, changing padding, changing margins, changing the width property of the linearlayout between match_parent, fill_parent, and wrap_content, but nothing changes this spacing. It is always the same.
Here is the relevant code:
LinearLayout tmpLL = (LinearLayout) convertView.findViewById(R.id.llUpgrades);
//remove previous list contents first
tmpLL.removeAllViews();
for(int i = 0; i<= tmpUpgradeList.size()-1; i++){
ImageView tmpIB = new ImageView(getContext());
Upgrade tmpUpgrade = tmpUpgradeList.get(i);
Upgrade.setUpgradePic(tmpIB, tmpUpgrade, tmpUpgrade.Title()==null);
tmpIB.setTag(position + ":" + i);
tmpIB.setPadding(5, 0, 0, 0);
tmpIB.setMaxWidth(50);
tmpLL.addView(tmpIB);
tmpIB.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String[] split = ((String) v.getTag()).split(":");
runUpgradePopup(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
}
});
tmpIB.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
String[] split = ((String) v.getTag()).split(":");
clearUpgrade(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
return true;
}
});
}
Layout of the one causing the error. The other layout puts the cards next to each other correctly but all I have different is that it is a linear layout and removed the relevant relative placement calls:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<Button
android:layout_width="100dp"
android:layout_height="60dp"
android:id="#+id/btnFRemoveShip"
android:text="Remove"/>
<ImageView
android:id="#+id/ivFRowShipIcon"
android:layout_height="60dp"
android:layout_width="75dp"
android:src="#android:drawable/ic_delete"
android:layout_marginLeft="10dp"
android:layout_toRightOf="#+id/btnFRemoveShip"/>
<TextView
android:layout_height="wrap_content"
android:ems="10"
android:layout_width="wrap_content"
android:id="#+id/tvFRowShipTitle"
android:text="error"
android:textSize="20dp"
android:layout_marginLeft="10dp"
android:layout_toRightOf="#+id/ivFRowShipIcon"/>
<HorizontalScrollView
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="75dp"
android:layout_marginTop="5dp"
android:layout_below="#+id/btnFRemoveShip">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/llUpgrades">
</LinearLayout>
</HorizontalScrollView>
</RelativeLayout>
Any help is greatly appreciated!
Took a couple days of rewording the question but finally found the answer.
Had to add this line before adding the image to the layout.
tmpIB.setAdjustViewBounds(true);
Thread is here: found answer
What is the proper way to expand a CardView?
Use an expandable list view with cardview
or even
You can use wrap content as height of cardview and use
textview inside it below title, so on click make the textview visible
and vice-versa.
but isn't it bad design ?
nope it isn't if you give some transition or animation when it's expanded
or collapsed
If you want to see some default transition then just write android:animateLayoutChanges="true" in parent layout.
If you are using CardViews inside a ListView or RecyclerView see my answer for recommended way of doing it:
RecyclerView expand/collapse items
If you are just using CardView then do this in your on onClickListener of cardview:
TransitionManager.beginDelayedTransition(cardview);
detailsView.setVisibility(View.VISIBLE);
By default keep the visibility of your detailsView to be GONE in your xml.
I used a cardview and an expand section item_description in the cardview. For the expand part I created a TextView below the header section (LinearLayout/item_description_layout) and when the user clicks on the header layout an expand/collapse method is called. Here is the code in the cardview:
<LinearLayout
android:id="#+id/item_description_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="48dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:orientation="horizontal">
<TextView
android:id="#+id/item_description_title"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.9"
android:gravity="center_vertical"
android:text="#string/description"/>
<ImageView
android:id="#+id/item_description_img"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.1"
android:layout_gravity="center_vertical|end"
app:srcCompat="#drawable/ic_expand_more_black_24dp"/>
</LinearLayout>
<TextView
android:id="#+id/item_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:gravity="center_vertical"
android:visibility="gone"
tools:text="description goes here"/>
Here is the method that is called. I also added a ObjectAnimator to handle the expand/collapse animation of the block. This is a simple animation that uses the length of the description text.
void collapseExpandTextView() {
if (mItemDescription.getVisibility() == View.GONE) {
// it's collapsed - expand it
mItemDescription.setVisibility(View.VISIBLE);
mDescriptionImg.setImageResource(R.drawable.ic_expand_less_black_24dp);
} else {
// it's expanded - collapse it
mItemDescription.setVisibility(View.GONE);
mDescriptionImg.setImageResource(R.drawable.ic_expand_more_black_24dp);
}
ObjectAnimator animation = ObjectAnimator.ofInt(mItemDescription, "maxLines", mItemDescription.getMaxLines());
animation.setDuration(200).start();
}
just a line of code before setting visibility GONE/ VISIBLE can do:
TransitionManager.beginDelayedTransition([the rootView containing the cardView], new AutoTransition());
no need to use the animateLayoutChanges=true in XML (this way also simple, but collapse behaviour is bad)
I originally tried doing this by just adding an extra View to the bottom of my CardView and setting its visibility to gone (gone vs invisible):
tools:visibility="gone"
Then, I set the CardView height to Wrap Content and added an icon with an onClickListener that changed the extra View's visibility to visible.
The issue with this was that even when the visibility was set to gone, my CardView was still behaving like the extra View was there.
To get around this, I explicitly set the visibility of the extra View to gone in my onBindViewHolder method:
holder.defPieces.visibility = View.GONE
After this, the expandable functionality worked how I wanted it to. I wonder if there's some odd order of operations that goes on when inflating a View to display in a RecyclerView.
mView.Click +=(sender, e) =>{
LinearLayout temp = mView.FindViewById<LinearLayout>(Resource.Id.LinerCart);
if (vs == false) {
temp.Visibility = ViewStates.Gone;
vs = true;
} else {
temp.Visibility = ViewStates.Visible;
vs = false;
}
};
I got the solution ( single cardview expandable listview )
check this link
http://www.devexchanges.info/2016/08/expandingcollapsing-recyclerview-row_18.html
if you add down arrow icon
you just use my code
create xml
<RelativeLayout
android:id="#+id/layout_expand"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:orientation="vertical">
<TextView
android:id="#+id/item_description_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#color/white"
android:clickable="true"
android:onClick="toggle_contents"
android:padding="10dp"
android:text="Guest Conditions"
android:textColor="#color/hint_txt_color"
android:fontFamily="sans-serif-medium"
android:textStyle="normal"
android:paddingBottom="15dp"
android:textSize="16dp"/>
<ImageView
android:layout_alignParentRight="true"
android:paddingTop="4dp"
android:paddingRight="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_keyboard_arrow_down"/>
<!--content to hide/show -->
<TextView
android:id="#+id/item_description"
android:layout_below="#+id/item_description_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:padding="10dp"
android:text="#string/about_txt2"
android:textColor="#color/hint_txt_color"
android:fontFamily="sans-serif-medium"
android:textStyle="normal"
android:paddingBottom="15dp"
android:visibility="gone"
android:textSize="12dp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
///////////////////////////////////////////////
Mainactivity.java
RelativeLayout layout_expand = (RelativeLayoutfindViewById(R.id.layout_expand);
item_description = (TextView) findViewById(R.id.item_description);
TextView item_description_title;
item_description_title = (TextView) findViewById(R.id.item_description_title);
item_description.setVisibility(View.GONE);
///////////////////////////////////////////////////////////////////
animationUp = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_up);
animationDown = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_down);
layout_expand.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(item_description.isShown()){
item_description.setVisibility(View.GONE);
item_description.startAnimation(animationUp);
}
else{
item_description.setVisibility(View.VISIBLE);
item_description.startAnimation(animationDown);
}
}
});
item_description_title.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(item_description.isShown()){
item_description.setVisibility(View.GONE);
item_description.startAnimation(animationUp);
}
else{
item_description.setVisibility(View.VISIBLE);
item_description.startAnimation(animationDown);
}
}
});