I'm a beginner Android developer (I'm a Windows developer since 1995) and I'm developing my first app.
I have a two dimensional array of data (something like a[2][]).
Basically it represents three different set of data that need to be represented on the device screen.
Unfortunately, while two of them can be represented using a grid, the third set must be represented in a completely different way.
So what I need is a three-paged activity; in the first two I would like to use a common layout, populated with the needed data, while in the third page I have to use a completely different layout.
I found this: ViewPager Without Fragments, but unfortunately I am confused on where to place code to handle user input on the three pages.
Being a beginner on Android I'm actually a bit confused on what should be the general approach in such a situation.
In my usual developement experience I would create a window with a tabbed control, three tabs, grids in the first two and some custom control in the third.
These three controls would live in the same "scope" (my window) and would be populated during window loading.
I understand that in Android I have to follow a completely different approach, but, as I said I'm confused: fragments? No fragments? What else?
Thank you!
To implement multiple pages within the same activity, you need to use fragments. In your case, we will need 3 fragments. Your activity layout file should contain a ViewPager which will hold the fragments in the activity. You can also add a TabLayout if you want the fragments to also be accessible via tabs.
A sample Activity layout can be as follows:
<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"
android:fitsSystemWindows="true"
tools:context="your.package.name.MainActivity">
<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:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:popupTheme="#style/AppTheme.PopupOverlay"
app:titleTextColor="#color/colorAccent"
/>
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill"
android:background="#color/grey"
app:tabGravity="fill"
app:tabIndicatorColor="#color/colorAccent"
app:tabIndicatorHeight="5dp"
app:tabSelectedTextColor="#color/colorAccent"
app:tabTextColor="#color/black"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
After doing this, you need to create a FragmentPagerAdapter class in your Activity Class. This adapter will manage the instantiation of your ViewPager which holds the fragments in the Activity.
Here is a sample FragmentPagerAdapter SubClass
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
Log.d(LOG_TAG, "postion " + position);
if (position == 0) {
return Fragment1.newInstance(position + 1);
} else if (position == 1) {
return Fragment2.newInstance(position + 1);
} else {
return Fragment3.newInstance(position + 1);
}
}
#Override
public int getCount() {
// Show 3 total pages.
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return "Fragment1";
case 1:
return "Fragment2";
case 2:
return "Fragment3";
}
return null;
}
}
This done, you can now get the reference to the ViewPager in the Activity onCreate method and the set the ViewPager adapter as follows
#Override
protected void onCreate(Bundle savedInstanceState) {
ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mSectionsPagerAdapter);
// setting the views to be accessed through tabs
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
int currentTab = tab.getPosition();
viewPager.setCurrentItem(currentTab);
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
Hope this clear for you. For more information, you can also check here
You can handle user input within your Activity, or you can break it up into Fragments. Both are described below.
Activity
The problem is usually you obtain references to Views in your layout during the Activity's onCreate() method and perform whatever setup (adding OnClickListeners, etc), but that's not really possible in this case because at that moment your ViewPager hasn't loaded any pages and the views on those pages aren't in the view hierarchy yet. (Android's view and layout system can be rather confusing when ViewPagers are involved).
Instead you would have to obtain the references and perform your setup inside of instantiateItem() of your adapter, and presumably invalidate those references in destroyItem().
Fragments
The issue described above is likely the reason Fragments are supported directly in the framework (using FragmentPagerAdapter or FragmentStatePagerAdapter). With a Fragment for each page, you can obtain references to views on that page and perform setup inside of onCreateView() (or onViewCreated()), and the business logic could also be encapsulated in the fragment. Basically, you don't have to worry about the existence of the views relative to the timing of these callbacks, unlike with the Activity's onCreate().
One other benefit is that by having logic encapsulated in separate Fragments, you have a degree of modularity with your app. If you can envision your app on a larger screen having perhaps two (or even all three) of your "pages" visible at once, then it's probably better to use Fragments from the beginning.
Related
In the application, I have:
One main activity
4 fragments
2.a. Home fragment
2.b. Data fragment
2.c. Picture fragment
2.d. Link fragment
So inside of the home fragment, there are 3 pictures which links to Data, picture and link fragment. There is a NavigationView which also links to the fragment.
Now, I want to add these links to each picture so it can work as a Navigation links and forwards to the same fragment. How to achieve that?
Currently, I am using setOnClickListener inside home fragment to forward each fragment but I believe there are better ways to do that? This is because when i use setOnClickListener this does not update the current nav link.
Layout
That is a very common task and Google introduced the Navigation component for such tasks. See https://developer.android.com/guide/navigation/navigation-getting-started for reference.
Some reference code (you also need the navigation graph):
In MainActivity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavController navController = Navigation.findNavController(this,
R.id.nav_host_fragment);
}
}
In MainActivity XML:
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#+id/bottomNavigationBorder"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/nav_graph" />
In "onViewCreated" of the navigation Fragment
NavController navController = Navigation.findNavController(view);
btnAction.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Bundle bundle = new Bundle();
bundle.putInt("ACTION", "1");
navController.navigate(R.id.action_mainFragment_to_nextFragment, bundle);
}
});
The function navController.navigate() can one or two arguments. The bundle is optional and is used if you want to send information to the target fragment. The "R.id....." attribute is defined in the navigation graph and describes the link between two fragments / activities.
Hope this helps.
I was already looking for various navigation solutions but I couldn't fit any for my app.
I have Parcelable objecs which are displayed in ListView using Array Adapter. When i click on item, it goes to another activity with another layout, dedicated to display this single item with image gallery, title etc. Therefore, i would like to add simple navigation as in picture which moves to next item if exist, previous item if exist and back to List View.
Here's some code
Single Item activity
public class SingleItem extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_item);
Intent intent = getIntent();
Single singleItem = intent.getParcelableExtra("Single");
String id = singleItem.getId();
Integer position = singleItem.getPosition();
String title = singleItem.getTitle();
String subtitle = singleItem.getSubtitle();
String text = singleItem.getText();
int[] image = singleItem.getImage();
TextView titleTV = findViewById(R.id.singleItemTitle);
LinearLayout gallery = findViewById(R.id.gallery);
LayoutInflater inflater = LayoutInflater.from(this);
for (int i = 0; i < image.length; i++) {
View view = inflater.inflate(R.layout.single_item_gallery, gallery, false);
ImageView imageView = view.findViewById(R.id.singleItemGalleryImage);
imageView.setImageResource(image[i]);
gallery.addView(view);
}
TextView descTV = findViewById(R.id.singleItemDesc);
titleTV.setText(title);
descTV.setText(text);
}
}
Here's my adapter
val listViewAdapter = ItemListAdapter(this, R.layout.adapter_view_layout, itemList)
listView.adapter = listViewAdapter
listView.setOnItemClickListener { _: AdapterView<*>, _: View, position : Int, _: Long ->
val intent = Intent(this, SingleItem::class.java)
intent.putExtra("Single", itemList[position])
startActivity(intent)
}
Single Item layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="#+id/singleItemTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/singleItemTitle">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="220dp"
android:paddingStart="10dp"
android:scrollbars="none"
android:paddingEnd="10dp">
<LinearLayout
android:id="#+id/gallery"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"/>
</HorizontalScrollView>
<TextView
android:id="#+id/singleItemDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
</RelativeLayout>
And i would like put navigation in layout above.
List view:
Single item navigation example:
Hello and welcome to StackOverflow (SO)!
As a newcomer you may not be aware, but, on SO, you should clearly define your question. I don't see a direct question. I'm deducing that you need a set of guidance in implementing your image gallery view using Android and to be able to swipe through them using the buttons (if I'm not mistaken).
Your approach is theoretically okay. What's left to do it to get a reference to the Next/Previous buttons from your SingleItem Activity and to manually scroll the HorizontalScrollView object when you press Next/Previous buttons.
However, your code has a major flaw in the following code segment.
for (int i = 0; i < image.length; i++) {
View view = inflater.inflate(R.layout.single_item_gallery, gallery, false);
ImageView imageView = view.findViewById(R.id.singleItemGalleryImage);
imageView.setImageResource(image[i]);
gallery.addView(view);
}
This is something which you should not be doing. Instead, use a RecyclerView which will only load your image when it's required to be displayed. You're essentially creating all the image views once you visit the detail view (SingleItem).
Your current approach might work okay for a few images, but is not recommended for a lot of images as it may
Slow down and freeze the app while the images load
You'll eventually run out of memory
Once you get this implemented with the RecyclerView, give it a shot and see whether it works. If you still have trouble, please don't hesitate to ask!
Cheers!
I see two options:
Instead of putting a whole Parcelable in the intent just put some kind of ID of the object. It can be a primary key, if you store them in DB or just an array index, if it's an array in memory kept in some singleton object. Then in SingleItem activity's onCreate just find the item by ID and load it. Therefore you can open more SingleItem activities from a SingleItem activity just by passing next or previous item ID to it.
Use fragments instead. You'll be having one activity, which holds a reference to the list of items, and two fragments one for the list view one and another for a single item display. Thus in single item fragment you can call a method on parent activity once "Next" button is clicked, which will then take the next item and create a replacement single item fragment with new data.
In example navigation action defined in navigation graph:
<action
android:id="#+id/action_fragment1_to_fragment2"
app:destination="#id/fragment2"
app:enterAnim="#anim/right_slide_in"
app:popExitAnim="#anim/left_slide_out"/>
When Fragment2 opens and starts sliding into view from the right, Fragment1 disappears instantly (sadly). When Fragment2 is closed and starts sliding to the right, Fragment1 is nicely visible under it, giving a nice stack pop effect (comparable to iOS).
How can I keep Fragment1 visible while Fragment2 slides into view?
EDIT:
This is not the most elegant solution, it is actually a trick but it seems to be the best way to solve this situation until the NavigationComponent will include a better approach.
So, we can increase translationZ (starting with API 21) in Fragement2's onViewCreated method to make it appear above Fragment1.
Example:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ViewCompat.setTranslationZ(getView(), 100f);
}
As very nice #xinaiz suggested, instead of 100f or any other random value, we can use getBackstackSize() to assign to the fragment a higher elevation than the previous one.
The solution was proposed by #JFrite at this thread
FragmentTransaction animation to slide in over top
More details can be found there.
In order to prevent the old fragment from disappearing during the sliding animation of the new fragment, first make an empty animation consisting of only the sliding animation's duration. I'll call it #anim/stationary:
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="#slidingAnimationDuration" />
Then in the navigation graph, set the exit animation of the action to the newly created empty animation:
<fragment android:id="#+id/oldFragment"
android:name="OldFragment">
<action android:id="#+id/action_oldFragment_to_newFragment"
app:destination="#id/newFragment"
app:enterAnim="#anim/sliding"
app:exitAnim="#anim/stationary"
</fragment>
The exit animation is applied to the old fragment and so the old fragment will be visible for the entire duration of the animation.
My guess as to why the old fragment disappears is if you don't specify an exit animation, the old fragment will be removed immediately by default as the enter animation begins.
It seems that you mistakenly used popExitAnim instead of exitAnim.
General rule is:
when you open (push) new screen, enterAnim and exitAnim take place
when you pop screen, popEnterAnim and popExitAnim take place
So, you should specify all 4 animations for each of your transitions.
For example, I use these:
<action
android:id="#+id/mainToSearch"
app:destination="#id/searchFragment"
app:enterAnim="#anim/slide_in_right"
app:exitAnim="#anim/slide_out_left"
app:popEnterAnim="#anim/slide_in_left"
app:popExitAnim="#anim/slide_out_right" />
Suppose your back stack currently contains:
A -> B -> C
and now from Fragment C, you want to navigate to Fragment D.
So your animation:
enterAnim -> Applied for D Fragment,
exitAnim -> Applied for C Fragment
Updated stack would be:
A -> B -> C -> D
Now you press the back or up button
popEnterAnim -> Applied for C Fragment,
popExitAnim -> Applied for D Fragment
now your back stack would be again:
A -> B -> C
TL;DR: enterAnim, exitAnim are for push, and popEnterAnim, popExitAnim are for pop operation.
I think using the R.anim.hold animation will create the effect you want:
int holdingAnimation = R.anim.hold;
int inAnimation = R.anim.right_slide_in;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(inAnimation, holdingAnimation, inAnimation, holdingAnimation);
/*
... Add in your fragments and other navigation calls
*/
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
Or just label it as you have within the action.
Here is the R.anim.hold animation mentioned above:
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="#android:integer/config_longAnimTime"
android:fromYDelta="0.0%p"
android:toYDelta="0.0%p"/>
</set>
In my own case the simplest solution was to use DialogFragment with proper animation and style.
Style:
<style name="MyDialogAnimation" parent="Animation.AppCompat.Dialog">
<item name="android:windowEnterAnimation">#anim/slide_in</item>
<item name="android:windowExitAnimation">#anim/slide_out</item>
</style>
<style name="MyDialog" parent="ThemeOverlay.MaterialComponents.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">#color/transparent</item>
<item name="android:windowAnimationStyle">#style/MyDialogAnimation</item>
</style>
Layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView 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:animateLayoutChanges="true"
android:background="#color/colorWhite"
android:fillViewport="true"
android:fitsSystemWindows="true"
android:layout_gravity="bottom"
android:orientation="vertical"
android:scrollbars="none"
android:transitionGroup="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
// Your Ui here
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
Java:
public class MyFragmentDialog extends DialogFragment {
#Nullable
#Override
public View onCreateView(
#NonNull LayoutInflater inflater,
#Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_dialog, container, false);
}
#Override
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
if (dialog != null) {
int width = ViewGroup.LayoutParams.MATCH_PARENT;
int height = ViewGroup.LayoutParams.MATCH_PARENT;
Objects.requireNonNull(dialog.getWindow())
.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
Objects.requireNonNull(dialog.getWindow()).setLayout(width, height);
dialog.getWindow().setWindowAnimations(R.style.MyDialogAnimation);
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.MyDialog);
}
}
Adding a slide animation is very easy using the new material motion library. Make sure to use the material theme version 1.2.0 or later.
For example, if you want to navigate from FragmentA to FragmentB with a slide animation, follow the steps mentioned below.
In the onCreate() of FragmentA, add an exitTransition as shown below.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = MaterialFadeThrough().apply {
secondaryAnimatorProvider = null
}
}
In the onCreate() of FragmentB, add an enterTransition as shown below.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough().apply {
secondaryAnimatorProvider = SlideDistanceProvider(Gravity.END)
}
}
The above code will create an animation fading out FragmentA and sliding in FragmentB.
Why not use ViewPager?
It will take care of the animations and maintain the correct lifecycle of your fragments. You will be able to update fragments as they change from within onResume().
Once you have your ViewPager set up, you can change fragments by swiping, or automatically jump to a desired fragment without worrying about hand-coding transformations, translations, etc.: viewPager.setCurrentItem(1);
Examples and more in-depth description:
https://developer.android.com/training/animation/screen-slide
In your activity layout XML:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:fillViewport="true">
<include
layout="#layout/toolbar"
android:id="#+id/main_toolbar"
android:layout_width="fill_parent"
android:layout_height="?android:attr/actionBarSize">
</include>
<com.google.android.material.tabs.TabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:minHeight="?android:attr/actionBarSize"/>
<androidx.viewpager.widget.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="fill_parent"/>
</LinearLayout>
In onCreate() of your Activity class:
ViewPager viewPager = null;
TabLayout tabLayout = null;
#Override
public void onCreate() {
...
tabLayout = findViewById(R.id.tab_layout);
viewPager = findViewById(R.id.pager);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
String[] tabs = new String[]{"Tab 1", "Tab 2"};
for (String tab : tabs) {
tabLayout.addTab(tabLayout.newTab().setText(tab));
}
PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager(), tabLayout);
viewPager.setAdapter(adapter);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
...
}
Your PagerAdapter class, which can reside within your Activity class:
public class PagerAdapter extends FragmentStatePagerAdapter {
TabLayout tabLayout;
PagerAdapter(FragmentManager fm, TabLayout tabLayout) {
super(fm);
this.tabLayout = tabLayout;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new your_fragment1();
case 1:
return new your_fragment2();
default:
return null;
}
return null;
}
#Override
public int getCount() {
return tabLayout.getTabCount();
}
}
Make sure to use the appropriate imports:
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
even though i googled extensively, i cant find a solution to my problem. I am new to programming, so please consider that in your answer.
I have a Main Activity and a Menu bar at the bottom. Since the menu bar is scrollable and i want to have it for all the different screens, i figured out that i could - instead of making an intent for a new activity - just put a fragment on top of the existing screen (and spare out the menu bar).
But i fail at programatically opening that fragment. I tried the following, but it doesnt even recognice the ID of the FrameLayout.
I was trying to replace a FrameLayout in my Main Activities' xml file with the fragment:
FragmentManager fragmentManager = getSupportFragmentManager();
android.support.v4.app.FragmentTransaction ft =
fragmentManager.beginTransaction();
ft.replace(R.id.idOfFrameLayout, new nameOfFragmentClass());
ft.commit();
EDIT:
It works after i implemented OnFragmentInteractionListener into the Main Activity. Thanks to everyone!
My original answer suggested using add instead of replace for the Fragment transaction. I have since learned that either can be used in this case. For more info on the differences, see this, this and this
Since my add/replace idea was off base, I've changed my answer to a simple Fragment tutorial. One Fragment inflated from one Activity. I've included the code for Fragment to Activity communication (the reason for the OPs problem) but left comments on what could be deleted if that's not needed. The only thing done in the Activity is launching the Fragment then receiving a message back from that Fragment. The Fragment has a button that will cause a message to be sent back to the Activity. The message will then be Logged.
In both classes, if no communications is needed from the Fragment to the Activity, delete anything that is marked TODO:
// this is what controls whether you use a Fragment from the support library
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class SimpleActivity extends AppCompatActivity
// TODO: delete "implements SimpleFragment.OnFragmentInteractionListener" from this
// line (leave the '{')
implements SimpleFragment.OnFragmentInteractionListener {
private static final String TAG = SimpleActivity.class.getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
SimpleFragment fragment = new SimpleFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
// TODO: this entire method can be deleted
#Override
public void onFragmentInteraction(String message) {
// This is where you can act on the message from the Fragment. You would do
// things that are done from an Activity or you may pass the message on
// to another Fragment.
Log.d(TAG, message);
}
}
Most import statements aren't shown, I left this one in to indicate using the support library
import android.support.v4.app.Fragment;
public class SimpleFragment extends Fragment {
private OnFragmentInteractionListener mListener; // TODO: delete this line
public SimpleFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_simple, container, false);
Button button = (Button)rootView.findViewById(R.id.msg_button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// TODO: delete these 3 lines, do something else with the button click
if (mListener != null) {
mListener.onFragmentInteraction("Message from Fragment");
}
}
});
return rootView;
}
// TODO: this entire method can be deleted
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
// TODO: this entire method can be deleted
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
// TODO: this entire method can be deleted
public interface OnFragmentInteractionListener {
void onFragmentInteraction(String message);
}
}
Now the layout files. 1st activity_simple.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment_container"
>
</FrameLayout>
</android.support.constraint.ConstraintLayout>
Then fragment_simple.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Info to Activity"
android:id="#+id/msg_button"
/>
</RelativeLayout>
This is a very basic sample. Here is a more extensive guide for Fragment usage.
When you use the 'replace' method, you need 3 things:
1- the container id, which is the view that is going to hold your fragment's view.
2- the fragment instance you want to use and
3- the fragment instance's tag, but this is optional.
Given that, let's say you have the following layout for your activity:
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
...
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.BottomNavigationView
android:id="#+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
app:menu="#menu/navigation" />
</android.support.design.widget.CoordinatorLayout>
The FrameLayout which id is "container" is the holding view, so that's the id you've gotta use:
ft.replace(R.id.container, new nameOfFragmentClass());
I suggest use this library to manage fragments:
FragmentManagerAndroid
EDIT 2:
In my MainActivity I have a function (displayData) that displays the substitutions for my school.
public void displayData(List<Schoolday> results) {
TabLayout mainTabLayout = (TabLayout) findViewById(R.id.mainTabLayout);
ViewPager mainViewPager = (ViewPager) findViewById(R.id.mainViewPager);
// draw the tabs depending on the days from the file
mainTabLayout.removeAllTabs();
for (int n = 0; n < results.size(); n++) {
List<Subject> subjectsToDisplay = new ArrayList<>();
if (myPreferences.getBoolean(SHOW_WHOLE_PLAN, true)) {
subjectsToDisplay = results.get(n).getSubjects();
} else {
List<Subject> tempSubjectsToDisplay;
tempSubjectsToDisplay = results.get(n).getSubjects();
for (int i = 0; i < tempSubjectsToDisplay.size(); i++) {
if (tempSubjectsToDisplay.get(i).getCourse().contains(myPreferences.getString(CLASS_TO_SHOW, "None"))) {
subjectsToDisplay.add(tempSubjectsToDisplay.get(i));
}
}
}
results.get(n).setSubjects(subjectsToDisplay);
// only create a tab if there's any information to show within that tab
if (results.get(n).getSubjects().size() > 0) {
mainTabLayout.addTab(mainTabLayout.newTab().setText(results.get(n).getDate().substring(0, 6)));
}
}
PagerAdapter pagerAdapter = new PagerAdapter(getSupportFragmentManager(), mainTabLayout.getTabCount(), results);
mainViewPager.setAdapter(pagerAdapter);
mainViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mainTabLayout));
mainTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
mainViewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
This is my main layout:
<?xml version="1.0" encoding="utf-8"?>
<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:id="#+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.design.widget.TabLayout
android:id="#+id/mainTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:elevation="6dp"
android:minHeight="?attr/actionBarSize"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:tabGravity="fill" />
<android.support.v4.view.ViewPager
android:id="#+id/mainViewPager"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_below="#id/mainTabLayout" />
</RelativeLayout>
And this is the layout of the fragment that represents a page in the ViewPager:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/mainSwipeContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/mainRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
Whenever a refresh is triggered by the SwipeRefreshLayout I need the function displayData to be called.
How can I call this function since the call comes from within a fragment/page from the ViewPager and displayData is not static?
Old question:
I've been searching around quite a while but couldn't find an answer. Maybe I've searched for the wrong thing - just pointing me to that would already help.
My project (Vertretungsplan on github) displays the substitution plan for my school which is available to view/download as an xml file (xml file from school website). I then display the data in my app.
I have a TabLayout (different tabs represent different days) and a connected ViewPager. Each page is using a fragment. Each fragment includes a RecyclerView (to display the results) which is wrapped in a SwipeRefreshLayout.
When I want to refresh the data with the SwipeRefreshLayout I need to download all the data again and then update all the pages as well as the TabLayout(a new day might have a been added so a new tab will be needed). Since my refresh happens inside a fragment but I'm referencing the TabLayout as well as the ViewPager from my MainActivity I have no clue how to properly access all the elements in order to update the content.
My idea was to set up a Broadcast to let my MainActivity know that it needs to refresh the page since it originally set up the whole layout but maybe there is a better solution for that?
I'm kinda new to stackoverflow so feel free to correct me in the way of asking things here! If there's anything other information you need just ask me!
I appreciate your help!!
There are a couple ways to implement what you are asking. The most direct way is to create an interface for the Activity that the Fragment uses. See the docs here:
http://developer.android.com/training/basics/fragments/communicating.html
The other option, which decouples the Fragment from the Activity (which some may see as an advantage) is to use an Intent. With that approach, you need to look into the onNewIntent method, as it is not obvious exactly how it works.
Either one should be fine, especially since you are exploring ideas at this point.