how is the recyclerView maintains its scroll position without any extra coding - android

Created a simple activity which has a recyclerView and displays the data list. When turned on the setting's 'don't keep activity alive' and set 'Background process limit' to 'No background process'. Expect the acitivity will be killed by os in background.
When minimize the app and reopen it, saw the activity's onCreate() is called on a new instance and it does all again to create a new set of recyclerView and adapter with new datalist.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
mRecyclerView = findViewById(R.id.recyclerView);
setSupportActionBar(toolbar);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(layoutManager);
}
But the ui still shows the item in last scrolled position (correctly restored).
Anyone knows how the previous position is transfered to the new recyclerView instance, etc.?
the layout:
<LinearLayout 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">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:titleTextColor="#android:color/white"
/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
</LinearLayout>
the activity:
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private ItemAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
mRecyclerView = findViewById(R.id. recyclerView);
setSupportActionBar(toolbar);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(layoutManager);
}
#Override
protected void onResume() {
super.onResume();
new FetchItemASyncTask(this).execute();
// in the asyncTask it will do setup adapter when data ready
// mAdapter = new ItemAdapter(dataList)
// mRecyclerView.setAdapter(mAdapter);
}
}

This is part of the default saving of state that Android performs for many views. The only thing you need to do to enable this is to give the view an android:id attribute (if you take that id away, you'll notice that it no longer saves the scroll position). The same is true for e.g. the text entered into an EditText.

Related

How to resolve a NullPointerException while using RecyclerView?

I am using two RecyclerViews in my app, one in the MainActivity and one in another activity. The one in the MainActivity works fine but the other has a NullPointerException and I don't know where the problem here is.
Here is a code snippet of the second RecyclerView, where the logcat says that there is a Null object reference. I used the same code as the first RecyclerView where everything works.
The error message says that there is a problem in this line: recyclerView.setLayoutManager(layoutManager);
and in this line
initRecyclerViewFriendOverView();
Here is where I implemented my second RecyclerView:
private void initRecyclerViewFriendOverView() {
LinearLayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
RecyclerView recyclerView = findViewById(R.id.recyclerViewFriendOverView);
recyclerView.setLayoutManager(layoutManager);
RecyclerViewAdapterFriendOverView adpater = new RecyclerViewAdapterFriendOverView(mNames, mImageUrls, this);
recyclerView.setAdapter(adpater);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play_overview);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
init();
initRecyclerViewFriendOverView();
getImages();
}
Here ist the XML where the second RecyclerView is used:
<?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"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerViewFreundesUebersicht"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Edit:
The problem was that I called the RecyclerView before initializing it in the onCreate method.
EDIT:
You are including another xml file in your activity using include statement, findViewById() won't be able to find views from included layouts. Give an id to your include statement, find that included view first and then call findViewById () in that view:
In Activity layout file:
<include id="includedRecyclerLayout"
....
</include>
And in your code:
ConstraintLayout includedLayout = findViewById(R.id.inckudedRecyclerLayout);
RecyclerView recyclerview = includedLayout.findViewById(R.id.RECYCLER_VIEW_ID);
If it shows error at recyclerView.setLayoutManager(layoutManager); it means your recyclerView is null. Then you look at where you initialized it:
RecyclerView recyclerView = findViewById(R.id.recyclerViewFreundesUebersicht);
It can be null because of two reasons:
You didn't add the RecyclerView to XML
Your R.id.recyclerViewFreundesUebersicht is referring to a view from another xml
Check your XML and make sure your recyclerview was added to XML and the id you are using here is same as the one used in activity.
Initialize LinearLayoutManager like this
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());

Toolbar not responding to Listview scroll events

I have an activity which contains a custom toolbar and a simple listview. I want the toolbar respond when the listview is scrolled. However, it is not working.
Here is the .xml file
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
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="com.abdralabs.talksee.HistoryActivity">
<android.support.design.widget.AppBarLayout
android:id="#+id/history_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/history_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/title"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/lv_history"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
</ListView>
Here is the .java file
public class HistoryActivity extends AppCompatActivity {
Toolbar toolbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_history);
toolbar = (Toolbar)findViewById(R.id.history_toolbar);
/*
setSupportActionBar(toolbar);
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
*/
toolbar.setTitle("History");
String[] rcArray = {"A","B","C","D","A","B","C","D","A","B","C","D","A","B","C","D","A","B","C","D","A","B","C","D","A","B","C","D"};
ArrayAdapter adapter = new ArrayAdapter<String>(this,
R.layout.list_history, rcArray);
ListView listView = (ListView) findViewById(R.id.lv_history);
listView.setAdapter(adapter);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
/*
switch (item.getItemId()){
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
break;
}
*/
return super.onOptionsItemSelected(item);
}
}
What have I done wrong?
EDIT:
In response to rafsanahmad007's answer
I have applied the changes as per your suggestion but still there is no change.
When I scroll through the ListView there is no change to the size of the ToolBar. However, when I click on the ToolBar itself and make an up & down motion the ToolBar moves up & down too. What I want to achieve is, when I scroll the ListView downwards the ToolBar should collapse and when I scroll the ListView upwards the ToolBar should get to its normal size/position.
The following pictures depict how the ToolBar is responding currently.
As you can see in the above pics, the ListView is not being scrolled, but the ToolBar itself is being scrolled. What I want is, when I scroll the ListView the ToolBar should scroll only during that time. I hope I have clarified myself.
try this:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:minHeight="80dp"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed" />
</android.support.design.widget.AppBarLayout>
you can also get Help from Here

Android - access fragment view from fragment container programatically

In MainActivity's onCreate, I replaced the FrameLayout fragment container in its xml layout with a DragSelectRecyclerView in MainFragment's xml layout using FragmentManager. The following code shows this:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, new MainFragment())
.commit();
}
MainActivity's xml:
<android.support.design.widget.CoordinatorLayout 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="com.gra.app.activities.MainActivity">
<FrameLayout
android:id="#+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="?attr/actionBarSize" />
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior=".utilities.AppBarLayoutBackgroundAlphaBehavior">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar" />
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
MainFragment's xml:
<com.afollestad.dragselectrecyclerview.DragSelectRecyclerView 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/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:dsrv_autoScrollEnabled="true"
app:dsrv_autoScrollHotspotHeight="56dp"
tools:context="com.gra.app.fragments.MainFragment" />
Is there any way to programmatically access an instance of DragSelectRecyclerView through FrameLayout?
The reason for this is I need to make the AppBarLayout inside MainActivity's xml dependent on MainFragment's DragSelectRecyclerView so I can fade the AppBarLayout in/out based on
DragSelectRecyclerView.getVerticalScrollbarPosition()
through
CoordinatorLayout.Behavior<AppBarLayout>
Is this possible or should I just move the DragSelectRecyclerView inside MainActivity's xml?
I would suggest you to have a public method in MainFragment -
public float getVerticalScrollbarPosition(){
return DragSelectRecyclerView.getVerticalScrollbarPosition();
}
Update MainActivity's onCreate method to this -
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainFragment mainFragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, mainFragment)
.commit();
}
Now within the MainActivity, you can access the method anywhere to get the value and fade the AppBarLayout in/out accordingly -
mainFragment.getVerticalScrollbarPosition();
This is how the communication between fragments and activity should be handled - https://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity
You could set the fragment directly in the xml layout of the main activity like
<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">
<fragment android:id="#+id/fragment"
android:name="some.package.MainFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
And then get the instance of DragSelectRecyclerView in the onCreate method of your activity by
DragSelectRecyclerView recyclerView = (DragSelectRecyclerView) findViewById(R.id.recyclerView)
Or you just set the DragSelectRecyclerView from your Fragment into your Activity:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
DragSelectRecyclerView dragSelectRecyclerView = (DragSelectRecyclerView) getView().findViewById(R.id.recyclerView);
Activity activity = getActivity();
if (activity != null && activity instanceof MainActivity) {
((MainActivity) activity).setDragSelectRecyclerView(dragSelectRecyclerView);
}
}
In both cases make sure you reset the variable after you fragment is gone.
#Override
public void onDestroyView() {
super.onDestroyView();
Activity activity = getActivity();
if (activity != null && activity instanceof MainActivity) {
((MainActivity) activity).setDragSelectRecyclerView(null);
}
}
you can try setting a tag in the fragment transaction and then retrieve the fragment and its view by the tag you set during the transaction.
you can do it like this:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, new MainFragment(),"tag") //Adding tag here
.commit();
}
and then later you can retreive the recyclerview inside that fragment by:
View fragmentView = getFragmentManager().findFragmentByTag("tag").getView();
DragSelectRecyclerView recyclerView = (DragSelectRecyclerView)fragmentView.findViewById(R.id.recyclerView) //use the view
but View fragmentView = getFragmentManager().findFragmentByTag("tag").getView(); may return null based on the state of the fragment's state. so you need to look out for that.

Android SwipeRefreshLayout NULL pointer exception SwipeRefreshLayout.setOnRefreshListener()

I'm trying to add pull to refresh functionality into listview.
In fragment_page.xml (here is defined global listview)
<LinearLayout
style="#style/AppTheme.BaseActivity"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
tools:context="xx.view.fragment.PageFragment">
<TextView
android:id="#android:id/empty"
style="#style/AppTheme.WrapContent.NoResult"
android:text="#string/no_results"/>
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swiperefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listSelector="#drawable/list_selector"/>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
And in Activity:
public class MessageActivity extends TabBaseActivity implements ManageView, OnRefreshListener {
private MessageController mMessageController;
private int mSelectedTab;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
setUp(new String[]{"Unread", "Read", "Sent", "All"}, Constants.Adapter.MESSAGE_ADAPTER);
mMessageController.loadMessages();
//START Swipe to refresh
try {
final SwipeRefreshLayout swipeContainer = (SwipeRefreshLayout) findViewById(R.id.swiperefresh);
if(swipeContainer == null) {
Logger.d("Null");
}
swipeContainer.setEnabled(false);
swipeContainer.setOnRefreshListener(this);
} catch (Exception e) {
Logger.e(e.getMessage());
}
//END Swipe to refresh
}
But swipeContainer is still null.
I tried to to it using the following tutorials:
http://www.androidhive.info/2015/05/android-swipe-down-to-refresh-listview-tutorial/
and
http://www.survivingwithandroid.com/2014/05/android-swiperefreshlayout-tutorial.html
But without the luck.
How can i solve it please?
Many thanks for any advice.
EDIT:
Content for view is set in init:
private void init() {
mMessageController = MessageController.getInstance(this, getSupportFragmentManager());
mMessageController.setView(this);
mSelectedTab = 0;
}
If you want to use your views in fragment (SwipeRefreshLayout, etc.), initialize your views in onActivityCreated() method. Once onActivityCreated() was called, you knew the activity was ready to be called upon.

Hide toolbar on scroll when the using a recyclerview inside a Fragment instead of an Activity

I'm trying to adapt the strategy for hiding / showing a toolbar (or any visual element) from the well explained and great article:
http://mzgreen.github.io/2015/02/15/How-to-hideshow-Toolbar-when-list-is-scroling%28part1%29/
But in my case I'm using a Fragment to hold the recycleview instead of the activity. My problem is that the padding is not being applied so the first element is under the toolbar, and I have also another strange behavior, as the toolbar is also under the statusbar. I don't know what is happening here.
The following are my "moving pieces":
BasicActivity.java: based on the one given on the previous post, but moving away the recycleview part as is going to be on the Fragment piece. Also it exposes the show and hide methods to allow the fragment to access it:
public class BasicActivity extends ActionBarActivity {
private Toolbar mToolbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_basic);
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container,new RecycleFragment())
.commit();
overridePendingTransition(0, 0);
initToolbar();
}
private void initToolbar() {
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
setTitle(getString(R.string.app_name));
mToolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
}
public void hideViews() {
mToolbar.animate().translationY(-mToolbar.getHeight()).setInterpolator(new AccelerateInterpolator(2));
}
public void showViews() {
mToolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2));
}
}
My activiy_basic.xml is the following:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<include layout="#layout/toolbar_actionbar" />
</FrameLayout>
The layout toolbar_actionbar.xml
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:clipToPadding="false"/>
The Fragment RecycleFragment.java:
public class RecycleFragment extends Fragment {
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_recycler, container, false);
return view;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initRecyclerView(view);
}
private void initRecyclerView(View view) {
RecyclerView recyclerView = (RecyclerView)view.findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
RecyclerAdapter recyclerAdapter = new RecyclerAdapter(createItemList());
recyclerView.setAdapter(recyclerAdapter);
recyclerView.setOnScrollListener(new HidingScrollListener() {
#Override
public void onHide() {
((BasicActivity)getActivity()).hideViews();
}
#Override
public void onShow() {
((BasicActivity)getActivity()).showViews();
}
});
}
private List<String> createItemList() {
List<String> itemList = new ArrayList<>();
for(int i=0;i<20;i++) {
itemList.add("Item "+i);
}
return itemList;
}
}
And the layout for the fragment is just a recyclerview fragment_recycler.xml:
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
The adapter and the viewholder for the recycler are the same as the article, and they doesn't affect the behavior.
What is wrong with the code?
UPDATE:
A MichaƂ Z. below pointed out. What was missing is the paddingTop and clipptoPadding on the Recyclerview view
So the final xml should be:
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize"
android:clipToPadding="false"/>
And to solve the statusbar overlapping problem, it is needed to add a "fitsystemwindows" = "true" element on the activity layout. So it must be as the following:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<FrameLayout android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<include layout="#layout/toolbar_actionbar" />
</FrameLayout>
UPDATE2
The fitSystemWindows is only needed if the theme is setting the statusbar as translucent
Your fragment_recycler.xml file is missing paddingTop and clipToPadding attributes.
It should look like this:
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize"
android:clipToPadding="false"/>
And also remove clipToPadding from your toolbar_actionbar.xml.

Categories

Resources