Background - New to Android, but pretty nifty with moving layouts around, understanding Java, Kotlin and XML. However this task seems to be way above my head.
Problem - I'm looking to convert the following Java file (RecyclerView) into a Kotlin file (ViewPager) - since I already have a ViewPager hooked-up with the same scrolling behaviour as desired. I get the impression it's a 10min job for a seasoned developer. If that's the case I wonder if I could call upon some assistance from the community? At least to work out where to start. I can't seem to find a guide on how to convert a RecyclerView into a PagerAdapter or ViewPager.
Essentially the existing ViewPager I'm using has static data (5 items) and this one could have tens of items, so needs to be dynamic with a separate datasource (items of CardItem).
RecyclerViewAdapter - Current (Java)
package com.APPNAME.fragments.cards;
import android.databinding.DataBindingUtil;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import com.APPNAME.R;
import com.APPNAME.databinding.FragmentCardsRecentlyViewedBinding;
import com.APPNAME.model.cardItem.CardItem;
public class RecentlyViewedAdapter extends RecyclerView.Adapter<RecentlyViewedAdapter.RecentlyViewedViewHolder> {
public OnCardClicked listener;
private ArrayList<CardItem> items = new ArrayList<>();
public void addItems(ArrayList<CardItem> list) {
items = list;
notifyDataSetChanged();
}
#Override
public RecentlyViewedViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
FragmentCardsRecentlyViewedBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.fragment_cards_recently_viewed, parent, false);
return new RecentlyViewedViewHolder(binding);
}
#Override
public void onBindViewHolder(RecentlyViewedViewHolder holder, int position) {
holder.binding.setViewModel(new RecentlyViewedViewModel(items.get(position)));
}
#Override
public int getItemCount() {
return items.size();
}
interface OnCardClicked {
void onCardClicked(View view, CardItem cardItem);
}
class RecentlyViewedViewHolder extends RecyclerView.ViewHolder {
FragmentCardsRecentlyViewedBinding binding;
public RecentlyViewedViewHolder(FragmentCardsRecentlyViewedBinding itemView) {
super(itemView.getRoot());
binding = itemView;
binding.cardView.setOnClickListener(v -> {
if (listener != null) {
listener.onCardClicked(v, items.get(getAdapterPosition()));
}
});
}
}
}
ViewPagerAdapter - Future (Kotlin)
package com.APPNAME.fragments.cards
import android.support.annotation.DrawableRes
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import java.util.*
import kotlinx.android.synthetic.main.fragment_cards_recently_viewed.view.*
import com.APPNAME.R
import com.APPNAME.activities.BaseActivity
import com.APPNAME.activities.cards.NewCardActivity
import com.APPNAME.model.cardItem.CardItem
import com.APPNAME.views.wrapContentViewPager.ObjectAtPositionPagerAdapter
class RecentlyViewedItemAdapter constructor(private val activity: BaseActivity) : ObjectAtPositionPagerAdapter() {
private var items = ArrayList<CardItem>()
override fun instantiateItemObject(container: ViewGroup, position: Int) : Any {
return getImageView(container, R.drawable.placeholder_card_image) { NewCardActivity.start(activity, it) }
}
private fun getImageView(container: ViewGroup, #DrawableRes imageResourceId: Int, onClick: (imageResourceId: Int) -> Unit = {}): View {
val layoutInflater = LayoutInflater.from(container.context)
val layout = layoutInflater.inflate(R.layout.fragment_cards_recently_viewed, container, false)
val image = layout.recentlyViewedImage
image.setImageResource(imageResourceId)
image.setOnClickListener { onClick(imageResourceId) }
container.addView(layout)
return layout
}
override fun isViewFromObject(view: View, anObject: Any) = (view == anObject)
override fun getCount() = 5 //Placeholder
override fun destroyItemObject(container: ViewGroup, position: Int, view: Any) {
container.removeView(view as View)
}
}
If you have relatively large number of items and need views to be recycled then the RecyclerView is the right option for you. It will be definitely easier than creating a custom view pager adapter with a fixed item count and a view holder pattern.
The default/generic PageAdapter is not recycling anything - it's added to make sure you have all your views initiated and ready to swipe through. However, you can use https://developer.android.com/reference/android/support/v4/app/FragmentStatePagerAdapter.html that will destroy/recreate the fragments when no longer used or reused.
Job done. I actually surprised myself by being able to modify the recycler view to mimmick the PageView's behaviour precisely (including padding, snap scrolling etc). Thanks to the simple subclass SnapHelper. Saved me a tonne of work, so no need to refactor the codebase.
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
PagerSnapHelper solved my problem.
binding.recyclerViewHomeAnnouncement.apply {
layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
binding.recyclerViewHomeAnnouncement.layoutManager = layoutManager
setHasFixedSize(true)
itemAnimator = DefaultItemAnimator()
adapter = homeAnnouncementAdapter
}
val snapHelper: SnapHelper = PagerSnapHelper()
snapHelper.attachToRecyclerView(binding.recyclerViewHomeAnnouncement)
Related
I'm trying to use a viewpager2 to show pictures taken from my app. Right now, I'm just using photos currently on my device to test, although the tutorial I followed (https://www.geeksforgeeks.org/viewpager2-in-android-with-example/) showed me how to make a viewpager2 with premade resource files. I'm trying to use an image bitmap instead, although whenever I run the code, the images come out blank. Is it an error with my files or is there a problem in my code? Here is my code:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
class ViewPager2Adapter extends RecyclerView.Adapter<ViewPager2Adapter.ViewHolder> {
// Array of images
// Adding images from drawable folder
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/DCIM/yes/yes_20220809_095311.jpg");
Bitmap bitmap2 = BitmapFactory.decodeFile("/sdcard/DCIM/yes/yes_20220809_095311___WITHTEXT.jpg");
private Bitmap[] images = {bitmap, bitmap2};
private Context ctx;
// Constructor of our ViewPager2Adapter class
ViewPager2Adapter(Context ctx) {
this.ctx = ctx;
}
// This method returns our layout
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(ctx).inflate(R.layout.images_holder, parent, false);
return new ViewHolder(view);
}
// This method binds the screen with the view
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
// This will set the images in imageview
holder.images.setImageBitmap(images[position]);
}
// This Method returns the size of the Array
#Override
public int getItemCount() {
return images.length;
}
// The ViewHolder class holds the view
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView images;
public ViewHolder(#NonNull View itemView) {
super(itemView);
images = itemView.findViewById(R.id.images);
}
}
}
PS: I am currently using hard coded files for testing, this will be changed later. The problem might be that the file path is wrong, but that is why I am asking this question.
I am trying to display CardViews inside a RecyclerView, each card will represent a cheese object.
This cheese object has 6 instance variables.
This is my Cheese.java :
public class Cheese {
private String CheeseName;
private String CheeseCountryOfOrigin;
private String CheeseDayMade;
private String CheeseDayExpire;
private String CheeseDescription ;
private String CheesePrice;
public Cheese(){} //Required for firebase
public Cheese(String CheeseName, String CheeseCountryOfOrigin, String CheeseDayMade, String CheeseDayExpire, String CheeseDescription, String CheesePrice) {
this.CheeseName = CheeseName;
this.CheeseCountryOfOrigin = CheeseCountryOfOrigin;
this.CheeseDayMade = CheeseDayMade;
this.CheeseDayExpire = CheeseDayExpire;
this.CheeseDescription = CheeseDescription;
this.CheesePrice = CheesePrice;
}
public String getCheeseName() {
return CheeseName;
}
public String getCheeseCountryOfOrigin() {
return CheeseCountryOfOrigin;
}
public String getCheeseDayMade() {
return CheeseDayMade;
}
public String getCheeseDayExpire() {
return CheeseDayExpire;
}
public String getCheeseDescription() {
return CheeseDescription;
}
public String getCheesePrice() {
return CheesePrice;
}
}
and this is my cheese_card.xml (I hardcoded some android:text for better understanding): cheese_card.xml
my RecyclerView is in a fragment.
This is my fragment:
fragment_cheeses_list.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/cheeses_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
all my cheese items are already in my Firebase Real-Time Database. To make my life simpler I am trying to use FirebaseUI to populate my RecyclerView with data from my Firebase database.
This is my CheesesListFragment.java, which is displayed in my MainActivity:
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.CardView;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.firebase.ui.database.FirebaseRecyclerAdapter;
import com.firebase.ui.database.FirebaseRecyclerOptions;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
public class CheeseListFragment extends Fragment {
private static final String TAG = "CheesesListFragment";
private FirebaseDatabase aFirebaseDatabase;
private DatabaseReference aCheesesDatabaseReference;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.e(TAG, "onCreateView Started Successfully");
//Create the recycler view object
RecyclerView cheesesRecycler = (RecyclerView) inflater.inflate(R.layout.fragment_cheeses_list, container, false);
//Add a grid layout manager to the recycler view
GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), 1);
cheesesRecycler.setLayoutManager(layoutManager);
cheesesRecycler.setHasFixedSize(true);
aFirebaseDatabase = FirebaseDatabase.getInstance();
aCheesesDatabaseReference = aFirebaseDatabase.getReference().child("cheeses");
//Query the cheeses in firebase db using firebaseUI instead of addChildEventListener
Query query = aCheesesDatabaseReference;
//configuration for the FirebaseRecyclerAdapter
FirebaseRecyclerOptions<Cheese> options =
new FirebaseRecyclerOptions.Builder<Cheese>()
.setQuery(query, Cheese.class)
.build();
FirebaseRecyclerAdapter adapter = new FirebaseRecyclerAdapter<Cheese, CheeseViewHolder>(options) {
#Override
public CheeseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Create a new instance of the ViewHolder, in this case we are using a custom
// layout called R.layout.cheese_card for each item
CardView cv = (CardView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.cheese_card, parent, false);
return new CheeseViewHolder(cv);
}
#Override
protected void onBindViewHolder(CheeseViewHolder holder, int position, Cheese model) {
CheeseViewHolder myHolder = (CheeseViewHolder)holder;
myHolder.cheeseName.setText(model.getCheeseName());
myHolder.cheeseCountryOfOrigin.setText(model.getCheeseCountryOfOrigin());
myHolder.cheeseDayMade.setText(model.getCheeseDayMade());
myHolder.cheeseDayExpire.setText(model.getCheeseDayExpire());
myHolder.cheeseDescription.setText(model.getCheeseDescription());
myHolder.cheesePrice.setText(model.getCheesePrice());
}
};
//Set the adapter to the recycle View
cheesesRecycler.setAdapter(adapter);
return cheesesRecycler;
}
public static class CheeseViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
TextView CheeseName;
TextView CheeseCountryOfOrigin;
TextView CheeseDayMade;
TextView CheeseDayExpire;
TextView CheeseDescription;
TextView CheesePrice;
public CheeseViewHolder (CardView v){
super(v);
cardView = v;
CheeseName = (TextView)cardView.findViewById(R.id.cheese_name);
CheeseCountryOfOrigin= (TextView)cardView.findViewById(R.id.cheese_origin);
CheeseDayMade= (TextView)cardView.findViewById(R.id.cheese_day_made);
CheeseDayExpire= (TextView)cardView.findViewById(R.id.cheese_day_expire);
CheeseDescription= (TextView)cardView.findViewById(R.id.cheese_description);
CheesePrice= (TextView)cardView.findViewById(R.id.cheese_price);
}
}
}
So my questions are: (answering any of them is welcomed and very helpful)
If i get it right, onCreateViewHolder is supposed to make ViewHolders for my Cheese object using my cheese_card.xml . if so, assuming I delete onBindingViewHolder am I suppose to see lots of view holders that look like my cheese_card.xml?
in onBindingViewHolder in setText : how can I get my TextViews to get a value from my firebase?
I am new to programming and not sure about onCreateViewHolder, onBindingHolder and cheesesViewHolder.I am not sure what every code I writed there means as some of them are copy-pasted.If I got it all wrong, can you please explain how can I reach my desired outcome, and what I did wrong?
Thank you, in advance :)
Modify onBindingViewHolder and cheesesViewHolder. Because in onBindingViewHolder you will bind data with Views not Views with they ids. Bind Views with they ids inside cheesesViewHolder. For example:
CardView cardView;
TextView cheese_name;
TextView cheese_origin;
public CheeseViewHolder(CardView v) {
super(v);
cardView = v;
cheese_name = (TextView) cardView.findViewById(R.id.cheese_name);
cheese_origin = (TextView) cardView.findViewById(R.id.cheese_origin);
// and so on...
}
Then inside onBindingViewHolder you will do something like this:
#Override
protected void onBindViewHolder(CheeseViewHolder holder, int position, Cheese model) {
cheesesViewHolder myHolder = (cheesesViewHolder)holder;
myHolder.cheese_name.setText(model.getCheeseName());
myHolder. cheese_origin.setText(model.getCheeseOrigin());
//and so on...
}
I was able to eventually fix my problem and get onCreateViewHolder and onBindViewHolder to start simply by adding
adapter.startListening();
to my onStart method. like this:
#Override
public void onStart() {
super.onStart();
Log.e(TAG,"onStart Started Successfully");
adapter.startListening();
}
And I edited the code using #Yupi suggestion.
my problem is - how to create custom list view with not just repeating one custom view, but like in Instagram or other app where list include other views in it, which it looks like scroll view with list view android other views in it, but Roman Guy says "List View in a Scroll view is a very poor way", and I'm agreed with it, don't believe which is Google use this way...
What are the best way to achieve this thing with ListView or Recycler View
To achieve that UI, you must define multiple view types for your Listview or Recyclerview; a very similar question has been answered here.
In your example, you will have two view types:
<Horizontal Scroll> which is an embedded horizontal Recyclerview/Listview.
<View> which is a view type that you have defined.
There are many tutorials on this concept. I would recommend you to use Recyclerview in your implementation due to its advantages over Listview.
Hey this is how your main fragment will look like :
package com.leoneo.stackoverflow;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.soulpatch.stackoverflow.R;
import java.util.ArrayList;
public class RecyclerViewExample extends Fragment {
private ArrayList<Object> mValues = new ArrayList<>();
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
final RecyclerView recyclerView = inflater.inflate(R.layout.recycler_view_example, container, false);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
final MultpleItemAdapter adapter = new MultpleItemAdapter(mValues);
recyclerView.setAdapter(adapter);
return recyclerView;
}
}
And here's your adapter code.
package com.leoneo.stackoverflow;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.soulpatch.stackoverflow.R;
import java.util.ArrayList;
public class MultpleItemAdapter extends RecyclerView.Adapter<MultpleItemAdapter.ViewHolder> {
private ArrayList<Object> mValues = new ArrayList<>();
public MultpleItemAdapter(ArrayList<Object> values) {
mValues = values;
}
enum ItemType {
TYPE_A,
TYPE_B;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 1:
//Inflate Type A layout
final LinearLayout linearLayoutA = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_a, parent, false);
//And pass the view to the ViewHolder
return new ViewHolder(linearLayoutA);
break;
case 2:
//Inflate Type B layout
final LinearLayout linearLayoutB = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_b, parent, false)
//And pass the view to the ViewHolder
return new ViewHolder(linearLayoutB);
break;
}
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
switch (getItemViewType(position)) {
case 1:
final TypeA typeA = (TypeA) mValues.get(position);
//Deal with the views that you defined for LinearLayout A
break;
case 2:
final TypeB typeB = (TypeB) mValues.get(position);
//Deal with the views that you defined for LinearLayout B
break;
}
}
#Override
public int getItemCount() {
return mValues.size();
}
#Override
public int getItemViewType(int position) {
final Object obj = mValues.get(position);
if (obj instanceof TypeA) {
return ItemType.TYPE_A.ordinal();
} else if (obj instanceof TypeB) {
return ItemType.TYPE_B.ordinal();
}
return super.getItemViewType(position);
}
static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
}
}
//Class that I want to be displayed in a CardView
private class TypeA {
}
//Class that I want to be displayed in a a LinearLayout
private class TypeB {
}
}
You can have as many classes as you want like TypeA and TypeB and add types to ItemType class as well accordingly.
Rest should be pretty self explanatory assuming you've worked with RecyclerViews before.
You are looking for different View types. It's possible with using these
GetViewTypeCount() this is an overridable method which returns how many view type you have in your listview-recycleview.
getItemViewType(int pos) get which item view type should return at the given position
For example if you want to add a different view in every 10 item, and there is only 2 type of views, you should use a code like this:
#Override
public int getItemViewType(int position){
if (position % 10 == 0)
return SECOND_ITEM;
return FIRST_ITEM;
}
#Override
public int getViewTypeCount() {
return 2;
}
And at the getView() function you can handle it with a switch-case or if-else structure like this:
switch (getItemViewType(cursor.getPosition())) {
case SECOND_ITEM:
//something to do
break;
default:
//something to do
}
You might want to use 2 different layout file to inflate in the switch-case statement above. However, if the layouts of both items are not different that much, I recommend to create just 1 layout, and make views inside it visible and gone according to item you want to get.
Oh and in case you don't know where to use them, you use them at your adapter class. The functions may vary as which kind of adapter you use however, they all work with the same logic.
And finally, I recommend you to use recyclerview. It is just a bit harder to implement than listview but it is a great substitution of listview which is more powerful and flexible. You can do a research for it.
Here is how you can achieve this like instagram, facebook etc. : You inflate a scrollable view at the given positions.
I hope the answer helps.
As always,
Have a nice day.
I have the following code :
MainActivity.java
package com.erc.library;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.FragmentTransaction;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
import com.erc.sayeghlibrary.adapter.TabsPagerAdapter;
public class MainActivity extends FragmentActivity implements
ActionBar.TabListener {
private ViewPager viewPager;
private TabsPagerAdapter mAdapter;
private ActionBar actionBar;
// Tab titles
private String[] tabs = { "Stories", "Dictionaries", "eBooks"};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int actionBarTitleId = Resources.getSystem().getIdentifier("action_bar_title", "id", "android");
if (actionBarTitleId > 0) {
TextView title = (TextView) findViewById(actionBarTitleId);
if (title != null) {
title.setTextColor(Color.WHITE);
}
}
// Initilization
viewPager = (ViewPager) findViewById(R.id.pager);
actionBar = getActionBar();
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
actionBar.setHomeButtonEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Adding Tabs
for (String tab_name : tabs) {
actionBar.addTab(actionBar.newTab().setText(tab_name)
.setTabListener(this));
}
/**
* on swiping the viewpager make respective tab selected
* */
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// on changing the page
// make respected tab selected
actionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// on tab selected
// show respected fragment view
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// action with ID action_refresh was selected
case R.id.Favorites:
Toast.makeText(this, "Favorites selected", Toast.LENGTH_SHORT)
.show();
break;
// action with ID action_settings was selected
default:
break;
}
return true;
}
}
TabsPagerAdapter.java
package com.erc.library.adapter;
import com.erc.library.Dictionaries;
import com.erc.library.Ebooks;
import com.erc.library.Stories;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
public class TabsPagerAdapter extends FragmentPagerAdapter {
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
return new Stories();
case 1:
return new Dictionaries();
case 2:
// Movies fragment activity
return new Ebooks();
}
return null;
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 3;
}
}
I'm making a library application in which the navigation is by tabs, the problem is each time I go from the third tab to the first or the first to the third, the tab content is refreshing, I want to prevent the refresh, Any help please ?
By default, ViewPager recreates the fragments when you swipe the page. To prevent this, you can try one of three things:
1. In the onCreate() of your fragments, call setRetainInstance(true).
2. If the number of fragments is fixed & relatively small, then in your onCreate() add the following code:
mViewPager = (ViewPager)findViewById(R.id.pager);
mViewPager.setOffscreenPageLimit(limit); /* limit is a fixed integer*/
3. Use a FragmentPagerAdapter as part of your ViewPager.
If I remember correctly, the second option is more promising. But I urge you to try all three and see which of them work.
one has to instance the FragmentPagerAdapter first, then .getCount() will return a value -
while .getCount() - 1 should be set as the default off-screen limit:
TabsPagerAdapter adapter = new TabsPagerAdapter(getSupportFragmentManager());
/* the ViewPager requires a minimum of 1 as OffscreenPageLimit */
int limit = (adapter.getCount() > 1 ? adapter.getCount() - 1 : 1);
ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(limit);
you can handle view recreation by check if the view is null or not
public class FragmentExample extends Fragment {
private View rootView;
public FragmentExample() {}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
if (rootView == null) {
rootView = inflater.inflate(R.layout.fragment_example_layout, container, false);
// Initialise your layout here
} else {
((ViewGroup) rootView.getParent()).removeView(rootView);
}
return rootView;
}
}
In the onCreate() of your fragments, call setRetainInstance(true)
A bit late to this question, but thanks to Y.S., got to know how ViewPager works. I was building an app with 4 tabs and at any point of time, noticed that only two tabs were being refreshed, which I suppose was a default behaviour. After hours of investigation, I understood that Android refreshes multiple tabs to bring in a smooth swiping performance for the user - you might notice that you would have clicked tab2, but android brings in the data for tab3 and keeps it ready.
Though this behaviour is good, it has its pros and cons. Pros - you get a smooth swiping experience, without data being loaded from an external server when you land up in that tab. Cons-your backstack implementation in tabs could go for a toss. When you click a tab, the view pager actually calls smoother tab and you'll end up in a big trouble, if your methods are setting up backarrow (home) at the top left based on what is in the backstack in the clicked tab.
setOffscreenPageLimit is the answer to this. If you want your custom backstack framework to function, and do not want tab3 to be called when tab2 is clicked, you simply need to set the value to the number of tabs. For instance, if you have 4 tabs, set setOffScreePageLimit(4). This would mean that Android would refresh all the 4 fragments initially, which is a bit of a performance overhead (which you should manage properly). But, your backstack and tab switching remain intact. Hope this helps.
Since the activity implements ActionBar.TabListener, the activity's onCreate() is getting called again and again. So place the following code in onResume() method:
// Initilization
viewPager = (ViewPager) findViewById(R.id.pager);
actionBar = getActionBar();
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
actionBar.setHomeButtonEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Adding Tabs
for (String tab_name : tabs) {
actionBar.addTab(actionBar.newTab().setText(tab_name)
.setTabListener(this));
}
/**
* on swiping the viewpager make respective tab selected
* */
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// on changing the page
// make respected tab selected
actionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
In my case above suggestion does not work.
To restrict recreation of fragment, what i did:
In onCreateView you can store inflated view in a global variable and initialize it only if it is null, like in this code:
var root:View?=null
var apiDataReceived=false
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
if (root==null)
root=inflater!!.inflate(R.layout.fragment_layout, container, false)
return root
}
Now if you are parsing some data and fill it into RecyclerView or any other View
Make a global variable like in above code apiDataReceived
Set it to true if you successfully parsed data.
Before apiCalls place a condition like this:
if (!apiDataReceived) {
apiCalls()
}
So if apiCalls() would be called only if data is not parsed.
Do your http calls and parsing or any other thing in method which called after onCreateView like onStart
The above code is in kotlin, If you are facing any issue, let me know in comments.
I am currently working on a project in which I was updating a ListView's adapter from ArrayAdapter to a Custom CursorAdapter.
I deleted the R.java intentionally to regenerate the R.java file after inserting a new <string></string> to the res/string.xml file since it was not automatically regenerating. The file did not generate.
I continued to debug, and ran a Project->Clean... in attempt to regenerate the R.java file. This did not work either.
After checking StackOverflow, I also tried changing the version in AndroidManifest.xml in hope to regenerate the file. This also did not work. I've tried a few other hacks in hopes something might have Eclipse regenerate the file. No Luck.
Does anyone have any idea? I am on a tight deadline and this is really holding me back.
Here is the file I was working in when the issue arose:
package com.renaissanceartsmedia.gradingapp.controllers.fragments;
import java.util.ArrayList;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.util.ArrayMap;
import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.TextView;
import com.renaissanceartsmedia.gradingapp.R;
import com.renaissanceartsmedia.gradingapp.controllers.activities.CourseActivity;
import com.renaissanceartsmedia.gradingapp.model.Course;
import com.renaissanceartsmedia.gradingapp.model.CourseStore;
import com.renaissanceartsmedia.gradingapp.model.GradingAppDatabaseHelper.CourseCursor;
public class CourseListFragment extends ListFragment {
// DEBUG
private static final String TAG = "CourseListFragment";
// Create an ArrayList<String> to store flashcards
ArrayList<Course> mCourses;
ArrayMap<Long, Course> mCoursesById;
// Cursor Object
private CourseCursor mCursor;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the Title of the Fragment's Activity
getActivity().setTitle(R.string.course_list);
// Load Cursor to DB
mCursor = CourseStore.get(getActivity()).queryCourses();
// Set the list of FlashcardListFragments
mCourses = CourseStore.get(getActivity()).getCourses();
mCoursesById = CourseStore.get(getActivity()).getCoursesById();
// Create an ArrayAdapter to use for displaying FlashcardListFragments in FlashcardListActivity
/*
ArrayAdapter<Flashcard> adapter = new ArrayAdapter<Flashcard>(
getActivity(),
android.R.layout.simple_list_item_1,
mFlashcards);
*/
// OLD METHOD
//ListItemLayoutAdapter adapter = new ListItemLayoutAdapter(mCourses);
CourseCursorAdapter adapter = new CourseCursorAdapter(getActivity(), mCursor);
// Set the adapter for the list
setListAdapter(adapter);
}
/* Handles a user selection of a FlashcardListFragment
*
*/
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
//Flashcard f = (Flashcard)(getListAdapter()).getItem(position);
// OLD WAY
//Course c = ((ListItemLayoutAdapter)getListAdapter()).getItem(position);
/*
Course c = ((CourseCursorAdapter))getListAdapter());
Log.d(TAG, c.getCourseTitle() + " was clicked");
// Start a new activity using an Intent
Intent openFlashcardDetail = new Intent(getActivity(), CourseActivity.class);
// Add EXTRAS to the intent
//openFlashcardDetail.putExtra(FlashcardFragment.EXTRA_FLASHCARD_ID, f.getId());
openFlashcardDetail.putExtra(Course.EXTRA_COURSE_ID, c.getId());
startActivity(openFlashcardDetail);
*/
}
// OLD METHOD
//class ListItemLayoutAdapter extends ArrayAdapter<Course> {
class CourseCursorAdapter extends CursorAdapter {
// Member Properties
// OLD METHOD
Course mCurrentListObject;
CourseCursor mCourseCursor;
// Constructor
// OLD METHOD
/*
public ListItemLayoutAdapter(ArrayList<Course> itemContent) {
super(getActivity(), 0, itemContent);
}
*/
public CourseCursorAdapter(Context context, CourseCursor cursor) {
super(context, cursor, 0);
mCourseCursor = cursor;
}
// OLD WAY
/*
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//If we weren't given a view, inflate a new view
if (convertView == null) {
convertView = getActivity().getLayoutInflater()
.inflate(R.layout.list_item_layout, null);
}
// Configure the view for this object
mCurrentListObject = getItem(position);
// Make Connections from Layout to Java code
TextView mainTextView = (TextView) convertView.findViewById(R.id.item_main_text);
TextView subTextView = (TextView) convertView.findViewById(R.id.item_sub_text);
// Set the Contents of the Views
mainTextView.setText(setMainText());
subTextView.setText(setSubText());
return convertView;
}
*/
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
// Use a layout Inflater to get a row view
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
// Get the course for the current row
Course course = mCourseCursor.getCourse();
// Setup a new TextView
TextView courseTitleTextView = (TextView)view;
String mainText = context.getString(R.string.course_title_hint, course.getCourseTitle());
courseTitleTextView.setText(mainText);
}
public String setMainText() {
return mCurrentListObject.getCourseTitle();
}
public String setSubText() {
return mCurrentListObject.getSubject();
}
}
}
I have solution, I hope this will help you. !!
Step 1) Change Build Target
For Example, if you selected 2.2 as Build Target then select Maximum you have. (like 4.4)
And if you selected 4.4 then select 4.3 as target.
Step 2) Clean Project
It will create R.java again
Thank you.
If you are on Eclipse open your problems view (Window -> Show view -> Problems) and look for an error in your resources or manifest
Try restarting eclipse, sometimes it will do the trick. Check if you are using jdk 7, it only needs jdk 6.
This usually happens because there is an issue somewhere in your XML or resource file names.
Try restarting Eclipse, if it still does not indicate where the problem is, try retracing your steps the last changes you made to XML files before this problem occurred. You will find some kind of syntax error or spelling mistake or invalid file name somewhere. Fix it then clean again.