Lesson 115. One application on different screens

Lesson 115. One application on different screens


In this lesson:

– take into account the orientation and screen size of the application

The snippet page on the official site lists the program code snippets. This application displays a list of article headings and the content of the selected article, and its appearance depends on the orientation of the screen. In horizontal orientation, it displays both headings and content (two snippets in one Activity). In vertical orientation, titles and content are displayed on different screens (two snippets are separated by two Activity).

The example is quite useful, and I decided it made sense to parse it in detail. I’ll change the code a little bit, but the overall meaning of the design will remain the same. Also, I think it will be useful if we create an application so that it will run on versions earlier than the third. And in addition, let’s make it work adequately on screens of different sizes.

Accordingly, the lesson consists of three parts.

1. An app that displays the headers on the left and the content on the right

2. Add orientation accounting. Vertical will display headings on the first screen and content on the second.

3. Add a screen size accounting. For smaller screens, any orientation will show headings on the first screen and content on the second.

Long thought of what to call the project. Decided – MultipleScreen.

Let’s create a project:

Project name: P1151_MultipleScreen
Build Target: Android 2.2
Application name: MultipleScreen
Package name: ru.startandroid.develop.p1151multiplescreen
Create Activity: MainActivity

Add rows to strings.xml:


	Header 1
	Header 2
	Header 3
	Header 4


	Content 1
	Content 2
	Content 3
	Content 4

The two arrays are headers and content.

Remember that we use the v4 library to make our snippet application run on older versions.

We create a snippet that will display a list of titles

TitlesFragment.java:

package ru.startandroid.develop.p1151multiplescreen;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class TitlesFragment extends ListFragment {

  public interface onItemClickListener {
    public void itemClick(int position);
  }

  onItemClickListener listener;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ArrayAdapter adapter = new ArrayAdapter(getActivity(),
        android.R.layout.simple_list_item_1, getResources()
            .getStringArray(R.array.headers));
    setListAdapter(adapter);
  }

  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);
    listener = (onItemClickListener) activity;
  }

  @Override
  public void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);
    listener.itemClick(position);
  }
}

The class inherits the ListFragment for convenience of the list.

onItemClickListener – an interface that will mimic Activity. We covered this scheme in detail in Lesson 106. The interface has an itemClick method, which the snippet will call when selecting a list item.

IN onCreate create an adapter with headers and pass it to the list.

IN onAttach write the Activity (to which the snippet is attached) in the listener. Of course, this Activity should implement the onItemClickListener interface.

IN onListItemClick, We send information about the selected item to the Activity via listener.

That is, this snippet will show us the headers and let Activity know which one was selected.

Create a second snippet to display the content.

layout file details.xml:



	
	

TextView that will display the content.

class DetailsFragment.java:

package ru.startandroid.develop.p1151multiplescreen;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class DetailsFragment extends Fragment {

  public static DetailsFragment newInstance(int pos) {
    DetailsFragment details = new DetailsFragment();
    Bundle args = new Bundle();
    args.putInt("position", pos);
    details.setArguments(args);
    return details;
  }

  int getPosition() {
    return getArguments().getInt("position", 0);
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.details, container, false);
    TextView tv = (TextView) v.findViewById(R.id.tvText);
    tv.setText(getResources().getStringArray(R.array.content)[getPosition()]);
    return v;
  }
}

method newInstance creates a snippet instance and writes to it the attributes of the number that came to the method input. This number will indicate the position of the selected item in the list of headings.

method getPosition takes a position from the arguments.

onCreateView creates a View, we find a TextView in it, and places content in that TextView with a position.

For us, the snippet arguments are new here. They can be set strictly before the snippet is attached to any Activity, that is, usually right after the snippet is created. They are stored in the snippet even after it has been rebuilt as a result, such as changing the orientation of the screen. The setArguments method lets you write arguments, and getArguments counts.

This snippet, when created, reads the contents of the position passed to it and outputs it to TextView.

Configure Activity. layout file main.xml:



	
	
	
	

To the left will be TitlesFragment with headers, and to the right we will place DetailsFragment in the FrameLayout container.

MainActivity.java:

package ru.startandroid.develop.p1151multiplescreen;

import ru.startandroid.develop.p1151multiplescreen.TitlesFragment.onItemClickListener;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity implements
    onItemClickListener {
  
  int position = 0;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    if (savedInstanceState != null)
      position = savedInstanceState.getInt("position");
    showDetails(position);
  }

  void showDetails(int pos) {
    DetailsFragment details = (DetailsFragment) getSupportFragmentManager()
        .findFragmentById(R.id.cont);
    if (details == null || details.getPosition() != pos) {
      details = DetailsFragment.newInstance(pos);
      getSupportFragmentManager().beginTransaction()
          .replace(R.id.cont, details).commit();
    }
  }

  @Override
  public void itemClick(int position) {
    this.position = position;
    showDetails(position);
  }

  @Override
  protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("position", position);
  }
}

Activity inherits the onItemClickListener interface to receive notifications of selected items from a snippet with a list of headers. The position field will store the last selected item. We save this field (onSaveInstanceState) and read (savedInstanceState in onCreate) when recreating Activity.

IN onCreate call the method that will show the last selected entry.

method showDetails gets an entry, looks for DetailsFragment. If it does not find or find but that displays data from another position, it creates a fragment again, passes it the desired position and places it in the container.

itemClick is a method called from a fragment with a list of headings. Here we get the position of the selected item in the list. We write it in the position field and call showDetails, which displays the required data on the screen.

We save everything, launch the application.

Select any item

let’s rotate the screen

everything works as it should.

Now let’s add a screen orientation account. In vertical orientation, MainActivity will only display headers. We will take the content snippet to a separate DetailsActivity

DetailsActivity.java:

package ru.startandroid.develop.p1151multiplescreen;

import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class DetailsActivity extends FragmentActivity {
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            finish();
            return;
        }
    
        if (savedInstanceState == null) {
            DetailsFragment details = DetailsFragment.newInstance(getIntent().getIntExtra("position", 0));
            getSupportFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
  }
}

Code from the rubric: “all words seem to be familiar, but what I wanted to say is unclear.” Let’s figure it out.

Let’s imagine the situation. We rotate the tablet vertically, only the headers will appear. We click on any title and go to DetailsActivity, which will show us the content (using DetailsFragment, of course). That is, we have a vertical orientation and see the content. Now rotate the tablet horizontally. What will be? DetailsActivity will be displayed on the entire horizontal screen and display content. But our concept says that the horizontal orientation of the application should show both the content and headings, because the width of the screen, because it allows. So, we need to get back to MainActivity.

We look at the first code snippet. The application specifies that the orientation is horizontal and in this case simply closes the Activity. And so this DetailsActivity we have will be called from MainActivity, then after finish we get into MainActivity and see what we need – both titles and content. And MainActivity stores the number of the selected header (regardless of orientation) and the content will appear the same as it was in DetailsActivity.

We look at the second fragment. We check that savedInstanceState == null – this means that Activity is created for the first time, not re-created after changing the screen orientation. Next, we create a DetailsFragment snippet using an INTENT item and place it in Activity.

Why create a snippet only when creating Activity and when recreating – no? Because the system itself can recreate existing fragments when rotating the screen, while maintaining the arguments of the fragment. And we have nothing at all in this case to recreate the fragment itself.

And here it should be understood that the system will not create a fragment at all through the newInstance method. She just doesn’t know the method. The system uses a constructor. And we can’t pass anything to this constructor to affect the behavior or content of the snippet. It is in such cases that the arguments come to the rescue. The system saves the arguments of the fragment when it is recreated. And with each re-creation, our snippet will know what content it should display because it uses arguments when creating a screen in the onCreateView method.

Don’t forget to spell out Activity in the manifest.

let’s create a folder res / layout-land and copy the basic layout there – res / layout / main.xml. That is, with the horizontal orientation, we will have everything as is.

AND res / layout / main.xml change as follows:



	
	

We have removed the content container, leaving only the headers. We get this screen in vertical orientation.

change MainActivity.java:

package ru.startandroid.develop.p1151multiplescreen;

import ru.startandroid.develop.p1151multiplescreen.TitlesFragment.onItemClickListener;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity implements
    onItemClickListener {

  int position = 0;
  boolean withDetails = true;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    if (savedInstanceState != null)
      position = savedInstanceState.getInt("position");
    withDetails = (findViewById(R.id.cont) != null);
    if (withDetails)
      showDetails(position);
  }

  void showDetails(int pos) {
    if (withDetails) {
      DetailsFragment details = (DetailsFragment) getSupportFragmentManager()
          .findFragmentById(R.id.cont);
      if (details == null || details.getPosition() != pos) {
        details = DetailsFragment.newInstance(pos);
        getSupportFragmentManager().beginTransaction()
            .replace(R.id.cont, details).commit();
      }
    } else {
      startActivity(new Intent(this, DetailsActivity.class).putExtra("position", position));      
    }
  }

  @Override
  public void itemClick(int position) {
    this.position = position;
    showDetails(position);
  }

  @Override
  protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("position", position);
  }
}

There is little change. a field is added withDetailsWhich will let us know: Shows Activity headers with or without content. In our case, this will coincide with the horizontal and vertical orientation.

IN onCreate we set the withDetails value by checking the container for the content snippet. If content is to be displayed, we call the showDetails method and pass the position to it. If the content is not to be displayed then we do nothing, we only show the headers.

In the method showDetails we look withDetails. If the content is to be displayed here, then the old algorithm works, we create a snippet. If the content is to be displayed in a separate Activity, then call DetailsActivity and pass it a position.

We save everything, launch the application.

select the title

rotate the screen

we only see headers

select the title

we see content in the new Activity

rotate the screen

we see content and headings

It is necessary to adjust the work of the program to small screens. Let me remind you that on small screens, we will, in any orientation, separate the titles and content by different Activity. The screen size can be defined differently. I will consider large screens large and xlarge. For these screens, the logic remains current, for others a little change.

The screen orientation is horizontal (land) and vertical (port). Screens will be distinguished: small, large and xlarge. Together we have 6 screen combinations.

1) small, port

2) large, port

3) xlarge, port

4) small, land

5) large land

6) xlarge, land

And we already have two file options main.xml – “content headers“AND”headers only“.

We need to compare combinations and variants.

Content Titles will be used in 5.6 combinations (large screens in horizontal orientation)

The header-only option is suitable for combinations 1,2,3,4 (small screens in any orientation and large screens in vertical orientation).

We create layout-folders under these combinations, and place variants of screens in them.

The res /layout-large-land. This is combination 5. Here we put the main.xml variant, which is “content headers.”

The res /layout-xlarge-land. This is combination 6. Here we put the main.xml variant, which is “content headers.”

The res /layout. This is all the rest of the combination (1,2,3,4). Here we put the main.xml variant, which is “only headers”.

That is, as a result, we in the folders layout-large-land and layout-xlarge-land copy the file from layout-land, and delete the folder layout-land. The layout folder is not touched, everything is ok.

There is little to change DetailsActivity.java:

package ru.startandroid.develop.p1151multiplescreen;

import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class DetailsActivity extends FragmentActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (getResources().getConfiguration().orientation 
        == Configuration.ORIENTATION_LANDSCAPE
        && isLarge()) {
      finish();
      return;
    }

    if (savedInstanceState == null) {
      DetailsFragment details = DetailsFragment.newInstance(getIntent()
          .getIntExtra("position", 0));
      getSupportFragmentManager().beginTransaction()
          .add(android.R.id.content, details).commit();
    }
  }

  boolean isLarge() {
    return (getResources().getConfiguration().screenLayout 
        & Configuration.SCREENLAYOUT_SIZE_MASK) 
        >= Configuration.SCREENLAYOUT_SIZE_LARGE;
  }

}

method isLarge determines whether or not the large screen is displayed. Previously, when we horizontally closed this Activity immediately, we will now only do this for large screens. And for others, the content will be displayed in a horizontal orientation.

We all save and launch the application.

Screenshots from the tablet will not lead. They will completely repeat the previous ones.

And on a smartphone with a small screen it will be like this:

select the title

rotate the screen

We push back and get to the headlines

Quite a few such material came out. But, as if, he told everyone what he wanted.

Just in case, I remind you that the architecture of the decision is not mine. I did an example that changed a little. I moved the logic from the title fragment to the main Activity and removed the use of the mode of selection and selection of list items. But I added work from qualifiers that allowed us to change the behavior of the program depending on the size and orientation of the screen.

If there are still unclear moments – velcom to the forum, we will decide.

In the next lesson:

– change the behavior of Activity in Task




Discuss in the forum [43 replies]

Leave a Comment