I have a LinearLayout with three children in it, two RecyclerViews and an ImageView. In the code based on some condition I enable and disable these children using setVisibility().
public void onNotificationPriorityMixChanged(int mix) {
Log.d(TAG, "onNotificationPriorityMixChanged() called with: mix = [" + mix + "]");
switch (mix) {
case MIX_BOTTOM_ONLY: {
topListView.setVisibility(GONE);
bottomListView.setVisibility(VISIBLE);
spacer.setVisibility(GONE);
}
break;
case MIX_TOP_ONLY: {
topListView.setVisibility(VISIBLE);
bottomListView.setVisibility(GONE);
spacer.setVisibility(GONE);
}
break;
case MIX_NONE: {
topListView.setVisibility(GONE);
bottomListView.setVisibility(GONE);
spacer.setVisibility(GONE);
}
break;
case MIX_BOTH: {
topListView.setVisibility(VISIBLE);
bottomListView.setVisibility(VISIBLE);
spacer.setVisibility(VISIBLE);
}
break;
default:
}
}
The layout file is has a LinearLayout with a first RecyclerView (topListView) followed by the ImageView (spacer) followed by again a RecyclerView (bottomListView) in it.
The problem is that I cannot get only one child view to occupy whole parent when other two are disabled.
My question is that should I use the layout_weight attribute in XML or dynamically. and can anyone tell me what is the connection between using setVisibility() and layout_weight attribute, if there is any?
Set the layout_height of your RecyclerViews to 0dp (for Vertical LinearLayout)
I have tested this working fine
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="2"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:layout_weight="1"
android:visibility="visible"
android:layout_width="match_parent"
android:layout_height="0dp"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="2dp" />
<android.support.v7.widget.RecyclerView
android:layout_weight="1"
android:visibility="visible"
android:layout_width="match_parent"
android:layout_height="0dp"/>
</LinearLayout>
for hiding any view use .setVisibility(View.GONE)
for showing any view use .setVisibility(View.VISIBLE)
Related
Just trying to center my RecyclerView horizontal.
Here is the xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
And here the java code:
mRecyclerView = findViewById(R.id.recycler_view);
itemLayoutManager = new GridLayoutManager(this, 6);
mRecyclerView.setLayoutManager(itemLayoutManager);
...
mItemAdapter = new ItemAdapter(MainActivity.this, mItemList);
mRecyclerView.setAdapter(mItemAdapter);
When I run the application the recyclerview aligns left.
What exactly do I have to do to center the content so that the margin is the same left and right like in the example picture on the right side?
Create a class like to set paddings to item:
class SpacingItemDecorator (private val padding: Int) : RecyclerView.ItemDecoration()
{
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
)
{
super.getItemOffsets(outRect, view, parent, state)
outRect.top = padding
outRect.bottom = padding
outRect.left = padding
outRect.right = padding
}
}
Now in Your RecyclerView set paddingStart and paddingEnd, e.g 4dp :
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="4dp"
android:paddingEnd="4dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="6"/>
When You create adapter just apply this item decorator:
val x = (resources.displayMetrics.density * 4).toInt() //converting dp to pixels
recyclerView.addItemDecoration(SpacingItemDecorator(x)) //setting space between items in RecyclerView
RecyclerView item:
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="6dp"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/txtContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{Integer.toString(dataView.id)}"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Result:
I faced the same issue recently and looked into the source code. There's no straight forward way to do this using GridLayoutManager. The other was is to use a GridView which is more customisable.
This is an alternate solution for you or any one else looking for a workaround.
I didn't want to change to a GridView so this is what I did. I set the RecyclerView's layout width to wrap_content and centered it horizontally within the parent (which in your case is a RelativeLayout so it will be something like android:centerHorizontally="true")
I knew the item width so I calculated the spanCount by dividing the screen width by the item width (plus any horizontal margins). Now I set the span count to the grid layout manager and it gets centered horizontally just how you want it.
Adding margins to you item's layout root play an important role here.
I'm using a custom LinearLayout (extends LinearLayout), so that and Adapter (BaseAdapter) can be attached to it.
(Thanks to the original author ofc.)
* #author Vincent Mimoun-Prat # MarvinLabs
*/
public class MyLinearLayout extends LinearLayout {
private static final String TAG = "MyLinearLayout";
private Adapter adapter;
....
private void reloadChildViews() {
removeAllViews();
if (adapter == null) return;
int count = adapter.getCount();
for (int position = 0; position < count; ++position) {
View v = adapter.getView(position, null, this);
if (v != null) {
int[] attrs =new int[]{R.attr.selectableItemBackground};
TypedArray typedArray =getContext().obtainStyledAttributes(attrs);
int backgroundResource =typedArray.getResourceId(0, 0);
v.setBackgroundResource(backgroundResource);
typedArray.recycle();
addView(v);
}
}
requestLayout();
}
}
So I'm basically adding each child dynamically via addView() method.
As you can see I've already attempted to place a ripple effect on each child view added programatically.
The child view that is added to this ViewGroup has the according attrs:
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<variable
name="contentProduct"
type="com.example.flyhigh.data.pojos_entities.content.variations.ContentProduct" />
<variable
name="touchListener"
type="android.view.View.OnTouchListener"/>
</data>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context="com.example.flyhigh.ui.DailyJournalFragment"
android:clickable="true"
android:focusable="true"
setOnTouchListener="#{touchListener}"
android:background="?android:attr/selectableItemBackground"
>
<TextView
.....
/>
</LinearLayout>
</layout>
The touchListener binded is working accordingly.
The LinearLayout:
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.example.flyhigh.adapters.PhaseTypePagerAdapter2"
>
<com.example.flyhigh.custom_artifacts.MyLinearLayout
android:id="#+id/myLinearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:orientation="vertical"
android:background="?selectableItemBackground" //I've tried with this on and off
android:clickable="true" //I've tried with this on and off
android:focusable="true" //I've tried with this on and off
android:divider="#drawable/divider"
android:dividerPadding="10dp"
android:showDividers="middle"
/>
</layout>
This LinearLayout is inside a ViewPager.
This ViewPager is working without FragmentManager (its Adapter is a PagerAdapter).
How do I know its the LinearLayout obfuscating the children and not the ViewPager obfuscating everything? because when playing around the LinearLayout attributes, the changes become visible, this includes the actual ripple effect of the LinearLayout itself (not its children) when touched outside a children (I've given some room for this to be possible).
In case you want to see the ViewPager:
<com.example.flyhigh.custom_artifacts.MyViewPager
android:id="#+id/main_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
>
I'm planning to add a Drag and Drop functionality to each children but I'm afraid that this symptom will reach that functionality also.
I'm 100% sure the solution relies on overriding a Method in the MyLayout class but which one?
I appear to have had some concepts wrong about the order of how views overlap.
It seems that the order will always be that the last nested object is the one on top of everything else (how could I've thought it otherwise...), maybe I was confused by the way the code is written (in a nested way)...
Anyways...
The problem wasn't that the parent ViewGroup was obfuscating its more inner children, BUT the other way around:
It's always the children that obfuscate its parent.
To reprise my initial question, I thought my outer layout (a custom LinearLayout) was preventing the ripple effect from its list items (a ViewGroup also).
So, the solution, without using the android:foreground= that seems to be a popular one, is the next one:
This inside the ViewGroup you want highlighted with ripple effect (my list item):
<LinearLayout
...
android:clickable="true"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"
android:addStatesFromChildren="true" // this is the important one, but without the other attrs is useless
>
//obfuscating children should have the according:
<TextView
...
android:clickable="true"
android:focusable="true"
...
/>
<TextView
...
android:clickable="true"
android:focusable="true"
...
/>
<TextView
...
android:clickable="true"
android:focusable="true"
...
/>
Etc...etc...etc...
</LinearLayout>
I am trying to add two different fragments into two containers in my activity. The containers are part of a collapsible view I have made:
collapsible_view.xml
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/collapsible_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:gravity="center"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/collapsible_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
The container I'm trying to use is collapsible_body.
I want to add two of these views to my activity layout and then add a different fragment in each collapsible_body. However, using fragmentTransaction.replace(R.id.collapsible_body...) does not specify which one of my two views collapsible_body's to replace.
Basically, the same as this question here: Fragment - replace container, if id is not unique
You should wrap the same id layouts in a parent layout. And then set the id for second container to a different integer. For example layout file would look like this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/parent_one">
<include layout="#layout/container" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/parent_two">
<include layout="#layout/container"/>
</LinearLayout>
</LinearLayout>
And you would access them from using parent ids first. Then setting the id for second container to a different one. For example:
val containerOneId = parent_one.container.id
val containerTwoId = 1
parent_two.container.id = containerTwoId
openFragment(TestFragment().apply {
val b = Bundle()
b.putString(TestFragment.ARG, "Fragment one")
arguments = b
}, containerOneId)
openFragment(TestFragment().apply {
val b = Bundle()
b.putString(TestFragment.ARG, "Fragment two")
arguments = b
}, containerTwoId)
Same access logic but in Java (in case you're not using Kotlin)
LinearLayout parentOne = (LinearLayout) findViewById(R.id.parent_one);
LinearLayout parentTwo = (LinearLayout) findViewById(R.id.parent_two);
FrameLayout containerOne = parentOne.findViewById(R.id.container);
FrameLayout containerTwo = parentTwo.findViewById(R.id.container);
int containerOneId = containerOne.getId();
int containerTwoId = 1;
containerTwo.setId(containerTwoId);
openFragment(new TestFragment(), containerOneId);
openFragment(new TestFragment(), containerTwoId);
I'm try to create a ListView with CardView. CardView contains always 3 rows with some info, but after that it got 2n rows that looks like:
- position, name;
- image, data, image, data.
I'm using for this task object, that contains:
- object with data, that will always fill fist 3 rows;
- list of object, that i use for 2n rows.
I've tried already:
- swapping RecyclerAdapter to ArrayAdapter (helps with visibility that I change too, but not with inflating);
- creating a method that will handle all logic related to inflating that layout
- inflating inside onBindViewHolder/getView
I will paste version with inflating CardView in another method:
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
/*inflating layout and fill it with data from first object*/
View listItem = convertView;
if(listItem == null)
listItem = LayoutInflater.from(context).inflate(R.layout.card,parent,false);
//add data
//if needed to make sure that inflating will occur once. list is
//LinearLayout inside CardView, current is entire object
if(list.getChildCount() < 1)
addList(list, current);
//setting click listeners and returning view
}
private void addList(ViewGroup parent, ListItem current){
for (Item var : ListItem.getItemList()) {
View layout = LayoutInflater.from(context).inflate(R.layout.card_part, parent, false);
//setting data
ViewGroup.LayoutParams params = layout.getLayoutParams();
params.height = LinearLayout.LayoutParams.WRAP_CONTENT;
params.width = LinearLayout.LayoutParams.WRAP_CONTENT;
layout.setLayoutParams(params);
parent.addView(layout);
}
}
#EDIT: CardView
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
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/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="#color/colorPrimary"
android:layout_margin="15dp"
app:cardCornerRadius="5dp"
app:cardElevation="25dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/id"
android:visibility="gone"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/text"
android:id="#+id/name"
android:textSize="20sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/text"
android:id="#+id/type"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="#color/text"
android:layout_weight="1"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_expand"
tools:ignore="ContentDescription"
android:id="#+id/show_list"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_hide"
tools:ignore="ContentDescription"
android:visibility="gone"
android:id="#+id/hide_list"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/list"
android:visibility="gone">
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
Actual results:
- if i comment
if(list.getChildCount() < 1)
data fill be sometimes added few time, not only from correct object.
- now with that if layout is inflating with wrong data.
Expected result:
Inflating inside CardView add data that is correct for object and connected to it list of objects.
#EDIT2:
I've tried to just create that part of View manually instead of using LayoutInflater. That does not change anything.
After some break from this topic I found way to do it. Adapter reusing old View on getView/onBindViewHolder. If Linear Layout that conatins earlier list of others elements like TextView, ImageView etc. was not cleared before adding new elements, old will stay.
The solustion is to remove old ones. On Linear Layout I needed to call removeAllViews() before adding new elements.
I've been trying to implement a layout similar to this from the material design guidelines, using the new Android Design Support Library. The CollapsingToolbarLayout seems to be the way to go, but it ignores any attempt to add elevation. Furthermore, if I set the height of the AppBarLayout to anything under about 144dp, the FloatingActionButton will pop out, but not return. Here's my xml:
<android.support.design.widget.CoordinatorLayout
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:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="144dp"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:expandedTitleMarginEnd="72dp"
app:expandedTitleMarginStart="72dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:paddingLeft="72dp"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_person_add_white_24dp"
app:borderWidth="0dp"
app:fabSize="mini"
app:layout_anchor="#id/appbar"
app:layout_anchorGravity="bottom|left|start"/>
After diving into the source code, I discovered the FloatingActionButton, when attached to the AppBarLayout, is set to appear only when the space available is <=appBarLayout.getMinimumHeightForVisibleOverlappingContent(), which is 2*minimum height(the Toolbar, in this case), plus the status bar. So in portrait on a phone, 56*2+26 = 138dp. I confirmed this by setting mine to 138dp, which works, and 137dp does not. If you want to get around this, you can either override getMinimumHeightForVisibleOverlappingContent() in AppBarLayout, or the following method in FloatingActionButton:
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
if(dependency instanceof SnackbarLayout) {
this.updateFabTranslationForSnackbar(parent, child, dependency);
} else if(dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout)dependency;
if(this.mTmpRect == null) {
this.mTmpRect = new Rect();
}
Rect rect = this.mTmpRect;
ViewGroupUtils.getDescendantRect(parent, dependency, rect);
if(rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
if(!this.mIsAnimatingOut && child.getVisibility() == 0) {
this.animateOut(child);
}
} else if(child.getVisibility() != 0) {
this.animateIn(child);
}
}
return false;
}
As for the CollapsingToolbarLayout elevation, I haven't found a workaround yet.
For CollapsingToolbarLayout elevation you can search in subclass CollapsingToolbarLayout.OffsetUpdateListener which has method onOffsetChanged doing the job
if(Math.abs(verticalOffset) == scrollRange) {
ViewCompat.setElevation(layout, layout.getTargetElevation());
} else {
ViewCompat.setElevation(layout, 0.0F);
}
Regarding the elevation, the collapsing toolbar layout explicitly removes the elevation when not collapsed*. It has been filed as a bug it seems:
https://code.google.com/p/android/issues/detail?id=192553&q=elevation&colspec=ID%20Status%20Priority%20Owner%20Summary%20Stars%20Reporter%20Opened
(As #Ben is saying in his answer and reference to the source code as well)
Direct link to the code (currently, line 1062):
https://android.googlesource.com/platform/frameworks/support/+/master/design/src/android/support/design/widget/CollapsingToolbarLayout.java#1062
if (Math.abs(verticalOffset) == scrollRange) {
// If we have some pinned children, and we're offset to only show those views,
// we want to be elevate
ViewCompat.setElevation(layout, layout.getTargetElevation());
} else {
// Otherwise, we're inline with the content
ViewCompat.setElevation(layout, 0f);
}