RecyclerView with Adapter does not keep values stored in Android - android-layout

I have the following simple recyclerview layout:
<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"
android:orientation="vertical"
tools:context=".SurvivorPicksheetActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
This recycler view links to the detailed layout of the list items as such:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout 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/rootView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/background"
android:layout_width="match_parent"
android:layout_height="180dp"
android:layout_centerHorizontal="true"
app:srcCompat="#drawable/gamefield_background" />
<ImageView
android:id="#+id/away"
android:layout_width="160dp"
android:layout_height="150dp"
android:layout_gravity="top|left"/>
<ImageView
android:id="#+id/home"
android:layout_width="160dp"
android:layout_height="150dp"
android:layout_gravity="top|right"
android:scaleX="-1"/>
</androidx.cardview.widget.CardView>
</FrameLayout>
</LinearLayout>
Inside my Adapter java class I have an setOnClickListener for the two ImageView items.
If a user clicks on them the item is selected while the old item is unselected.
The problem I am encountering if that lets say the list of items has 25 items. If I click on the lets say 3rd item, everything works as expected.
Then I scroll to the end of the list, and scroll back up to the top, the selection is not longer valid even thought I clicked/selected prior to scrolling to the end of the list.
I can reclick and item at the top, scroll down and scroll back up and my selection is gone!?
Does anyone know why this is happening and more importantly how i can resolve it!?
UPDATE:
The following is my Adapter class:
public class GameAdapter extends
RecyclerView.Adapter<GameAdapter.GameViewHolder> {
// variable that holds the selected team
private String selectedTeam = "";
#Override
public void onBindViewHolder(GameViewHolder holder, int position) {
final Games game = gameList.get(position);
holder.awayTeamImageView.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v) {
if (selectedTeam.equals(String.valueOf(game.getHomeId()))) {
// RESET PLAYER SELECTION
selectedTeam = "";
selectedGame = "";
} else {
// SET PLAYER SELECTION
selectedTeam = String.valueOf(game.getHomeId());
selectedGame = String.valueOf(game.getKey());
}
}
}
}
class GameViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public GameViewHolder(#NonNull View itemView) {
super(itemView);
itemView.setOnClickListener(this);
awayImageView = itemView.findViewById(R.id.away);
homeImageView = itemView.findViewById(R.id.home);
homeImageView.setOnClickListener(this);
awayImageView.setOnClickListener(this);
}
}
Based on my code above, I 'think' I am setting and unsetting the selected team so that when I scroll to the end and back to the top the selected team should remain vs being recycled and not displaying anymore?!

That's because the old Views are being recycled, back to the initial state. And this is a natutral behavior of RecyclerView.
In order to make each item View "memorize" its check/select state, you need to create a field in your model class to record the state, like isChecked or isSelected.

You can use own domain ViewHolder and ViewModel for recycleview.
Android developer documents gives basic understanding of how to create dynamic RecycleView in below link.
https://developer.android.com/guide/topics/ui/layout/recyclerview

This is expected as the views are recycled and lose their values accordingly whenever you scroll up/down the list; further for configuration changes (like screen rotation) the selected views will be lost.
To fix this, you should go for ViewModel to have persistent data even for configuration changes.
But, as a rescuer, assuming the adapter provided list of games gameList is in a persistent storage like mentioned above; then you can:
Create a selection boolean in the Games model class with getter/setter which has a false initially.
When a row is selected from the RecyclerView, then set the boolean to true like gameList.get(selectedPosition).setIsSelected(true)
Then notifyItemChanged() of that position.

Related

Android: RecyclerView's CardView Not Showing Every Time

I have a RecyclerView in MainActivity that shows a list of CardViews and that is working properly. A click on the CardView finishes the RecyclerView Activity and launches a Detail Activity that shows the clicked on CardView in a new RecyclerView list. The Detail Activity is used only to show that single CardView in a RecyclerView (I do this so I can use RecyclerView's ItemTouchHelper.SimpleCallback code on the CardView for easy left swipe for the user).
Here is the problem: I hit the left caret on the Detail Activity's Toolbar to return to the MainActivity. Then a click on the exact same CardView brings the user back to the Detail Activity. But this time only the background View (a border) is showing. The view of the CardView and its database data is completely missing.
The error appears to happen randomly. I can click to go from the MainActivity to the Detail Activity back and forth 5 times successfully and then on the sixth try, no CardView will show in the Detail Activity. Or I'll click two times successfully and then the third time, the CardView in the Detail Activity will not show. Note the left caret click in Detail Activity uses onBackPressed() so the Detail Activity finishes. So I don't think there should be any backstack issues. I also tried to adjust the xml height for the CardView to match_parent rather than wrap_content but no luck. The Detail Activity's ViewModel to Repository to Dao returns a List wrapped in LiveData. Perhaps there is an observer problem with the ViewModel, but I thought the observer gets removed/destroyed when the Detail Activity is destroyed? What am I missing here?
Adapter
...
itemHolder.cardView.setOnClickListener(view -> {
Card adapterItem= TodosAdapter.this.getItem(itemHolder.getAdapterPosition());
int adapPos = itemHolder.getAdapterPosition();
if (adapPos !=RecyclerView.NO_POSITION) {
onItemClick(adapPos, adapterItem);
}
});
MainActivity
...
public void onItemClick(int clickPos, Card cardFromClick) {
Intent intent = new Intent(MainActivity.this, DetailActivity.class);
intent.putExtra("TAG","fromMain");
intent.putExtra("itemFromMain", cardFromClick);
startActivity(intent);
finish();
DetailActivity
...
public class DetailActivity extends AppCompatActivity {
private int cardId = -1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
// Get a new or existing ViewModel from the ViewModelProvider.
detsViewModel = new ViewModelProvider(this).get(CardViewModel.class);
Toolbar toolbar = findViewById(R.id.toolbar);
// The left caret is for Up navigation to the previous activity
// for OS versions 4.0 and earlier.
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
toolbar.setNavigationIcon(R.drawable.ic_action_previous_item);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onBackPressed();
}
});
}
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
String classname = extras.getString("TAG");
// The user clicked on a Card in the MainActivity
if (classname != null && classname.equals("fromMain")) {
card = extras.getParcelable("itemFromMain");
if (card != null) {
cardId = card.getId(); // card data is stored in Room database.
}
}
}
detsViewModel.getSingleCard(cardId).observe(this, singleAdapterList -> {
adapter2.setCardList(singleAdapterList);
});
}
activity_details.xml
...
<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"
android:background="#FFFFFF"
tools:context=".DetailsActivity" >
<include
android:id="#+id/toolbar"
layout="#layout/toolbar" >
</include>
<RelativeLayout
android:id="#+id/todoListLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/toolbar" >
<TextView
android:id="#+id/Card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="Card"
android:textStyle="bold"
android:textColor="#color/text_primary"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_centerHorizontal="true" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/details_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_below="#+id/Card"
android:scrollbars="vertical" />
<TextView
android:id="#+id/skytext5"
android:text="Cards"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/details_recyclerview"
android:background="#color/colorPrimary"
android:textAppearance="?android:attr/textAppearanceMedium"
android:clickable="true"
android:focusable="true" />
</RelativeLayout>
DetailsAdapter
...
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.details_list_item, parent, false);
}
private List<Card> oneCardList
public void setCardList(List<Card> singleCardList) {
if (oneCardList != null) {
oneCardList.clear();
this.oneCardList = singleCardList;
} else {
// First initialization
this.oneCardList = singleCardList;
}
}
details_list_item.xml
...
<FrameLayout
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/detsinglecard_view"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical"
android:foreground="?android:attr/selectableItemBackground"
android:background="#FFFFFF"
tools:context=".DetailActivity">
<RelativeLayout
android:id="#+id/view_background2"
android:layout_width="wrap_content"
android:layout_height="match_parent"
...
</RelativeLayout>
<RelativeLayout
android:id="#+id/view_foreground2"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="#color/colorFlLabelFinal" >
<androidx.cardview.widget.CardView
android:id="#+id/cardview_dets"
android:layout_height="match_parent"
android:layout_width="match_parent"
...
}
ViewModel
...
LiveData<List<Card>> getSingleCard(int cardId) {
return repository.getSingleCard(cardId);
}
Repository
...
public LiveData<List<Card>> getSingleCard(int cardId) {
return quickcardDao.getSingleCard(cardId);
}
Dao
...
#Query("SELECT * FROM cards WHERE cardId = :cardId LIMIT 1")
LiveData<List<Card>> getSingleCard(int cardId);
So if the data does not change then going back to the same DetailActivity will not refresh the View. The answer was to re-use the LiveData (rather than re-loading the LiveData again from the database) if the data has not changed. See the Android Developers Architecture Components guide for ViewModel, "Implement a ViewModel" section for the "loadUsers()" example that solved my problem: https://developer.android.com/topic/libraries/architecture/viewmodel.

Single Activity with Navigation Component: how to handle different AppBar / Themes

I've been using the new Navigation Component since shortly after it has been announced at Google I/O, and also started to embrace the single-activity as much as possible.
The Single Activity allowed me to share ViewModels between view for an awesome experience and I really don't want to go back to multi-activity if I'm not forced to.
But there's something that gets in the way: AppBar / Themes (status bar) to the single activity concept.
This is part of the design I'm working in:
As you can see there are different requirments for how the Actionbar / status bar should look.
It's a simple drawer with standard actionbar
Classic detail with image going under the translucent status bar, supposed to use CollapsingToolbarLayout to turn into a standard actionbar when scrolling up
In this case it is non-standard actionbar, I'd call it a "floating toolbar" cause it doesn't expand to the full with of the screen and contains an already expanded SearchView / EditText
Fairly standard AppBar with tabs
List of issues that arise from leaving the single activity:
can't share ViewModels between activities
complex navigations which re-use parts already defined in another activity navigation graph have to be duplicated / moved into a dedicated activity
back navigation "re-construction" doesn't work between activities
Those are issues I want to avoid if possible, but how do you guys manage these kind of situation on a single-activity with navigation component. Any idea?
As mentioned here, the developer document said
Adding the top app bar to your activity works well when the app bar’s layout is similar for each destination in your app. If, however, your top app bar changes substantially across destinations, then consider removing the top app bar from your activity and defining it in each destination fragment, instead.
I was also thinking the same but never got time to do some experiment. So it's not a solution, it's an experiment, where I want to replace a view with another, here, the toolbar with a toolbar that contains an ImageView.
So I created a new Application using "Basic Activity" template. Then created two destinations within the graph, Home and destination. And lastly, created another layout for Toolbar:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar 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="?actionBarSize">
<ImageView
android:id="#+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="#mipmap/ic_launcher_round" />
</androidx.appcompat.widget.Toolbar>
The activity_main.xml has:
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
...
And then within Activity, of-course depends on the setup, but let's say that I want to setup an support-actionbar with toolbar:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
Toolbar toolbar2 = (Toolbar) getLayoutInflater().inflate(R.layout.destination_toolbar, null);
AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
navController = Navigation.findNavController(this, R.id.nav_host_fragment);
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph())
.build();
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
switch (destination.getId()) {
case R.id.homeFragment:
appBarLayout.removeAllViews();
appBarLayout.addView(toolbar);
setSupportActionBar(toolbar);
toolbar.setTitle("Home Fragment");
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
break;
case R.id.destinationFragment:
appBarLayout.removeAllViews();
appBarLayout.addView(toolbar2);
setSupportActionBar(toolbar2);
toolbar2.setTitle("");
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
break;
}
});
}
And thus, this works, making it somewhat ugly as destination grows and new Toolbar/any other view is being added.
P.S. As I told earlier, this is just an experiment, if anyone has a better solution, please do post a new answer.
Disclaimer
Based of #Rajarshi original experiment, I made a working solution for this problem. I'm not sure is the most elegant, or if there are better ways. But after hours of research and investigation, this is the best solution I found.
Solution
Inflate the toolbars separately and store their references so they are not picked by the garbage collector.
Then load each on demand in your main AppBarLayout inside a custom OnDestinationChangedListener defined for your navController
Example
Here's an example I've written in Kotlin.
On your activity.xml layout, define an AppBarLayout that is empty.
layout/activity.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
...
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay" />
...
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Define the toolbars that your app needs to have in separate layout files.
layout/toolbar_defaul.xml
<com.google.android.material.appbar.MaterialToolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/default_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:menu="#menu/menu_default"
app:popupTheme="#style/AppTheme.PopupOverlay" />
layout/toolbar2.xml
<com.google.android.material.appbar.MaterialToolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/toolbar2"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:menu="#menu/menu2"
app:popupTheme="#style/AppTheme.PopupOverlay" />
In your main (and only) activity, declare AppBar related components as class properties, so that they are not picked up by the garbage collector.
Activity.kt
class Activity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var appBarLayout: AppBarLayout
private lateinit var defaultToolbar: MaterialToolbar
private lateinit var toolbar2: MaterialToolbar
...
And finally, in the onCreate method, define a OnDestinationChangedListener for the navController. Use it to load on demand each toolbar.
Activity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_ryvod)
// Set up AppBar
appBarLayout = findViewById(R.id.appbar)
appBarConfiguration = AppBarConfiguration(setOf(R.id.StartFragment))
defaultToolbar = layoutInflater.inflate(R.layout.toolbar_default, appBarLayout, false) as MaterialToolbar
toolbar2 = layoutInflater.inflate(R.layout.toolbar2, appBarLayout, false) as MaterialToolbar
val host =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
?: return
val navController = host.navController
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.locationPickerFragment -> {
appBarLayout.removeAllViews()
appBarLayout.addView(toolbar2)
setSupportActionBar(toolbar2)
}
else -> {
appBarLayout.removeAllViews()
appBarLayout.addView(defaultToolbar)
setSupportActionBar(defaultToolbar)
}
}
setupActionBarWithNavController(navController, appBarConfiguration)
}
}
That should do the trick
I confronted this problem a while ago, with similar UX/UI as yours:
Sidenav Navigation Drawer
A "normal" Appbar with back arrow
Translucent Appbar/status bar
My solution was having a different .xml Appbar for each case and using the <include/> tag inside every fragment xml:
<include
android:id="#+id/include"
layout="#layout/default_toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
The window config for case 1 and case 2 was the same, but for the translucent Appbar, the window config changed, see case 3.
So I had to do a config change every time the fragment showed up/replaced:
public static void transparentStatusBar(Activity activity, boolean isTransparent, boolean fullscreen) {
if (isTransparent){
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}else {
if (fullscreen){
View decorView = activity.getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
} else {
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
| View.SYSTEM_UI_FLAG_VISIBLE);
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
}
And then use this method in translucent appbar/status bar fragment's lifecycle:
#Override
public void onResume() {
super.onResume();
UtilApp.transparentStatusBar(requireActivity(), true, true);
}
#Override
public void onStop() {
super.onStop();
UtilApp.transparentStatusBar(requireActivity(), false, false);
}

How to create activity with text view, a button and a toast?

I am basic. in android studio, I want to start a basic project.
I want to type "any word" in a plain text view.
Only whenever I click on a button ,Just we can see that word as a TOAST.
this case I need : a plan text view, a button , and a toast.
How should i do this?
Thanks for the replies.
In your xml file you need to set an id for both textviews and button
In your activity:
//get the button
button = (Button) findViewById(R.id.but);
txt1= (text1) findViewById(R.id.text1);
txt2 = (text2) findViewById(R.id.text2);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v)
{
txt2.setText(txt1);
}
Your XML code should look like so. Note the id's of the EditText and the TextView which are crucial for extracting your strings and setting them respectively.
Also note the 'onClick' function on your button which is necessary to call upon the method in your MainActivity (Java Class) with the same name.
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/enterText"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="changeText"
android:text="Show Now"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/newText"/>
</LinearLayout>
Now for your MainActivity (Java) Class method:
public void changeText(View view){
EditText editText = findViewById(R.id.enterText);
String res = editText.getText().toString();
TextView textView = findViewById(R.id.newText);
textView.setText(res);
}
Again take note of the declaration of the EditText object using your 'id', "enterText" and "newText" respectively.
Hope this helps.

Android Floating Action Button Behaviour with Adview

I'm sorry if this has been already answered but it's been a while and I'm still searching. Since the FAB has a behaviour class you can assign to it which will work with scrolling inside a coordinator layout, I was wondering if it was possible to include behaviour to make the FAB automatically get placed above an adview when it is visible similar to how it reacts to a snack bar. Thank you in advance.
Work Around!
I figured out a work around as I was just playing around some time back, even forgot about the question. Instead of putting my adview inside a container with the rest of my views I simply had to wrap the coordinator view in a Relative layout and set the coordinator layout above the adview ID. I'm sure this might not be the best way to do it.
So what I ended up having is this: NB My adview is set to visibility gone by default and I only set the visibility when the adrequest is complete
<RelativeLayout
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">
<android.support.design.widget.CoordinatorLayout
android:layout_above="#+id/ad_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
android:visibility="gone"
app:layout_behavior="utils.FabBehavior_Main"
app:rippleColor="#color/fab_ripple"
app:srcCompat="#drawable/ic_fab" />
</android.support.design.widget.CoordinatorLayout>
<com.google.android.gms.ads.AdView
android:id="#+id/ad_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone"
app:adSize="SMART_BANNER"
app:adUnitId="#string/banner_ad_unit_id"
/>
</RelativeLayout>
The utils.FabBehavior_Main is the relative package class that contains the behaviour properties for the view, utils being the package. So in your own application you could have the name space com.myapp.utils.ScrollAwareFab which would have be like this:
public class ScrollAwareFab extends FloatingActionButton.Behavior {
public ScrollAwareFab(Context context, AttributeSet attrs) {
super();
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target,
nestedScrollAxes);
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child,
View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
/**
* Called when a FloatingActionButton has been hidden
*
* #param fab the FloatingActionButton that was hidden.
*/
#Override
public void onHidden(FloatingActionButton fab) {
super.onShown(fab);
fab.setVisibility(View.INVISIBLE);
}
});
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
}

android UI - wrap_content for viewpager

I have linear layout with viewpager on top and another vew (button in this example) bottom. Views in the pager have different heights. I would like the pager to have wrap_content height on all pages and button view to fill the rest of the screen.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:text="New Button"
android:id="#+id/button3"
android:layout_weight="1"/>
</LinearLayout>
This doesn't work. If I give layout_weight=1 to both pager and button, they share screen 50:50 no matter of content height. I tried to call requestLayout() in ViewPager.OnPageChangeListener.onPageSelected() but it doesn't help. Also tried this
Have you tried setting the layout_weight=1 for the pager and the layout_weight=2 for the button?
This works for me, but I don't consider it perfect.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View titlestrip = getChildAt(0);
int h = titlestrip.getMeasuredHeight();
View fv = ((FragmentPagerAdapter)getAdapter()).getItem(getCurrentItem()).getView();
if (fv != null) {
fv.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
h += fv.getMeasuredHeight();
}
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY));
}
It relies on the fact that my FragmentPagerAdapter.getItem() doesn't instantiate new fragments but return it from container.

Resources