Статьи

Фрагмент в Android: учебник с примером с использованием WebView

В этом посте мы хотим объяснить, как использовать фрагмент в Android с реальным примером . В последнем посте мы говорили о фрагменте в Android и о том, как мы можем использовать его для поддержки нескольких экранов. Мы описали их жизненный цикл и как мы можем его использовать. В этом посте мы хотим углубиться и создать пример, который поможет нам лучше понять, как мы можем использовать фрагменты. В качестве примера мы хотим создать простое приложение, которое состоит из двух фрагментов :

  • тот, который показывает список ссылок и
  • другой, который показывает веб-страницу в WebView.

Мы можем предположить, что у нас есть два разных макета, один для портретного режима и один для

пейзаж . В ландшафтном режиме мы хотим что-то вроде изображения ниже:

в то время как в портретном режиме мы хотим что-то вроде:

СОЗДАТЬ МАКЕТ

Первый шаг, который мы должны сделать, это создать наш макет. Как мы уже сказали, нам нужно два разных макета, один для портрета и один для пейзажа. Поэтому нам нужно создать два xml-файла в res / layout (для портретного режима) и один в res / layout-land (для ландшафтного режима). Конечно, мы могли бы настроить больше нашего макета, включая другие спецификации, но пока этого достаточно. Эти два файла называются activity_layout.xml .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >
 
    <fragment android:id="@+id/listFragment"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              class="com.survivingwithandroid.fragment.LinkListFragment"/>
 
</RelativeLayout>

Это для портретного режима, и, как мы заметили, у нас есть только один фрагмент, содержащий только список ссылок. Позже мы увидим, как создать этот фрагмент. Более того, нам нужен другой макет, как это видно из рисунка выше, тот, который содержит WebView .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 
    android:layout_width="match_parent"
 
    android:layout_height="match_parent"
 
    android:orientation="vertical" >
 
    <WebView android:id="@+id/webPage"
 
             android:layout_height="wrap_content"
 
             android:layout_width="wrap_content"/>
 
</LinearLayout>

Для ландшафтного режима у нас есть нечто очень похожее плюс компонент FrameLayout в том же макете.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:orientation="horizontal" >
 
    <fragment android:id="@+id/listFragment"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              class="com.survivingwithandroid.fragment.LinkListFragment"
              android:layout_weight="2"/>
 
    <FrameLayout android:id="@+id/fragPage"
                 android:layout_width="0dp"
                 android:layout_height="match_parent"
                 android:layout_weight="4"
                 />
 
</LinearLayout>

СОЗДАТЬ ФРАГМЕНТ СПИСКА ССЫЛКИ

Как вы уже заметили, оба макета имеют общий фрагмент, который называется LinkListFragment . Мы должны создать это. Если вы еще не читали пост, объясняющий жизненный цикл, самое время взглянуть. В этом случае нам не нужно переопределять все методы в жизненном цикле фрагмента, но те, которые важны для управления его поведением. В нашем случае нам нужно переопределить:

  • onCreateView
  • onAttach

В этом фрагменте мы используем простой ListView для отображения ссылок и простой адаптер для настройки способа отображения элементов. Мы не хотим тратить много времени на создание собственного адаптера, потому что это не входит в эту тему, вы можете обратиться сюда, чтобы получить больше информации. Просто помните, что в методе фрагмента onCreateView мы просто раздуваем макет и инициализируем пользовательский адаптер. В качестве разметки XML для раздувания во фрагменте имеем:

01
02
03
04
05
06
07
08
09
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
 
    <ListView android:id="@+id/urls"
              android:layout_height="match_parent"
              android:layout_width="wrap_content"/>
 
</LinearLayout>

в то время как в методе выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    Log.d("SwA", "LV onCreateView");
    View v = inflater.inflate(R.layout.linklist_layout, container, false);
    ListView lv = (ListView) v.findViewById(R.id.urls);
    la = new LinkAdapter(linkDataList, getActivity());
    lv.setAdapter(la);
    lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
         public void onItemClick(AdapterView<?> parentAdapter, View view, int position,
                 long id) {
            LinkData data = ( (LinkAdapter) la ).getItem(position);
            ( (ChangeLinkListener)  getActivity()).onLinkChange(data.getLink());
        }
 
    });
    return v;
}

В этом методе мы просто устанавливаем наш собственный адаптер и устанавливаем слушателя, когда пользователь нажимает на элемент. Мы расскажем об этом позже (если вам интересно, смотрите межфрагментную связь).

В методе onAttach мы проверяем, что действие, которое содержит фрагмент, реализует определенный интерфейс.

1
2
3
4
5
6
7
8
@Override
    public void onAttach(Activity activity) {
        // We verify that our activity implements the listener
        if (! (activity instanceof ChangeLinkListener) )
            throw new ClassCastException();
 
        super.onAttach(activity);
    }

Мы выясним, зачем нам этот контроль позже.

ФРАГМЕНТНАЯ СВЯЗЬ

В основном в нашем примере у нас есть два фрагмента, и они должны обмениваться информацией, чтобы при выборе пользователем элемента во фрагменте 1 (LinkListFragment) другой (WebViewFragment) отображал веб-страницу, соответствующую ссылке. Поэтому нам нужно найти способ, позволяющий этим фрагментам обмениваться данными.

С другой стороны, мы знаем, что фрагмент — это фрагмент кода, который можно повторно использовать внутри другого действия, поэтому мы не хотим связывать наш фрагмент с определенным действием, чтобы не аннулировать нашу работу. В Java, если мы хотим разделить два класса, мы можем использовать интерфейс. Так что это интерфейсное решение идеально подходит. С другой стороны, мы не хотим, чтобы наш фрагмент обменивался информацией напрямую, потому что каждый фрагмент может полагаться только на активность, которая его содержит. Таким образом, самое простое решение состоит в том, что действие реализует интерфейс.

Поэтому в нашем случае мы определяем интерфейс с именем ChangeLinkListener, который имеет только один метод:

1
2
3
4
public interface ChangeLinkListener {
 
    public void onLinkChange(String link);
}

Более того, мы должны убедиться, что наша деятельность реализует этот интерфейс, чтобы быть уверенными, что мы можем вызвать его. Лучшее место для проверки это метод onAttach (см. Выше), и в конце нам нужно вызвать этот метод, когда пользователь выбирает элемент в ListView:

1
2
3
4
5
6
7
8
9
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
     public void onItemClick(AdapterView<?> parentAdapter, View view, int position,
             long id) {
        LinkData data = ( (LinkAdapter) la ).getItem(position);
        (ChangeLinkListener)  getActivity()).onLinkChange(data.getLink());
    }
 
});

ПРОГРАММИРОВАНИЕ ОСНОВНОЙ ДЕЯТЕЛЬНОСТИ: НАЙТИ ФРАГМЕНТ

К настоящему моменту мы говорили только о фрагментах, но мы знаем, что фрагменты существуют внутри «отцовской» деятельности, которая их контролирует. Таким образом, мы должны создать эту деятельность, но мы должны сделать гораздо больше.

Как мы уже говорили ранее, эта деятельность должна реализовывать пользовательский интерфейс, чтобы он мог получать данные из LinkListFragment. В этом методе (onLinkChange) мы должны каким-то образом контролировать, находимся ли мы в ландшафтном режиме или в портретном режиме, потому что в первом случае нам нужно обновить WebViewFragment, тогда как во втором случае мы должны запустить другое действие. Как мы можем сделать это? Разница в макете заключается в наличии FrameLayout . Если он присутствует, это означает, что мы находимся в ландшафтном режиме, иначе в портретном режиме. Итак, код в методе onLinkChange:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public void onLinkChange(String link) {
    System.out.println("Listener");
    // Here we detect if there's dual fragment
    if (findViewById(R.id.fragPage) != null) {        WebViewFragment wvf = (WebViewFragment) getFragmentManager().findFragmentById(R.id.fragPage);
 
        if (wvf == null) {
            System.out.println("Dual fragment - 1");
            wvf = new WebViewFragment();
            wvf.init(link);
            // We are in dual fragment (Tablet and so on)
            FragmentManager fm = getFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
 
            //wvf.updateUrl(link);
            ft.replace(R.id.fragPage, wvf);
            ft.commit();
 
        }
        else {
         Log.d("SwA", "Dual Fragment update");
         wvf.updateUrl(link);
        }
    }
    else {
        System.out.println("Start Activity");
        Intent i = new Intent(this, WebViewActivity.class);
        i.putExtra("link", link);
        startActivity(i);
    }
 
}

Давайте проанализируем этот метод. Первая часть (строка 5) проверяет, существует ли FrameLayout. Если он существует, мы используем FragmentManager, чтобы найти фрагмент относительно WebViewFragment. Если этот фрагмент нулевой (то есть мы используем его впервые), мы просто создаем его и помещаем этот фрагмент «внутрь» FrameLayout (строка 7-20). Если этот фрагмент уже существует, мы просто обновляем URL (строка 23). Если мы не находимся в ландшафтном режиме, мы можем начать новую деятельность, передавая данные как намерение (строка 28-30).

ФРАГМЕНТ И ВЕБВИАКТИВНОСТЬ WEBVIEW: веб-страница

Наконец, мы анализируем фрагмент WebViewFragment. Это действительно просто, это просто переопределить некоторый метод Fragment, чтобы настроить его поведение:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class WebViewFragment extends Fragment {
 
    private String currentURL;
 
    public void init(String url) {
        currentURL = url;
    }
 
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {       
        super.onActivityCreated(savedInstanceState);
    }
 
    @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
 
        Log.d("SwA", "WVF onCreateView");
        View v = inflater.inflate(R.layout.web_layout, container, false);
        if (currentURL != null) {
            Log.d("SwA", "Current URL  1["+currentURL+"]");
 
            WebView wv = (WebView) v.findViewById(R.id.webPage);
            wv.getSettings().setJavaScriptEnabled(true);
            wv.setWebViewClient(new SwAWebClient());
            wv.loadUrl(currentURL);
 
        }
        return v;
    }
 
   public void updateUrl(String url) {
        Log.d("SwA", "Update URL ["+url+"] - View ["+getView()+"]");
        currentURL = url;
 
        WebView wv = (WebView) getView().findViewById(R.id.webPage);
        wv.getSettings().setJavaScriptEnabled(true);
        wv.loadUrl(url);
 
    }
 
    private class SwAWebClient extends WebViewClient {
 
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            return false;
        }
 
    }
 
}

В методе onCreateView мы просто раздуваем наш макет внутри фрагмента и проверяем, что отображаемый URL не является нулевым. Если это так, мы просто показываем страницу (строка 15-30). В updateUrl мы просто находим компонент WebView и обновляем его URL.

В портретном режиме мы сказали, что нам нужно запустить другое действие, чтобы показать веб-страницу, поэтому нам нужно действие (WebViewActivity). Это действительно просто, и я просто показываю код без каких-либо комментариев к нему:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public class WebViewActivity extends FragmentActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {       
        super.onCreate(savedInstanceState);
 
        WebViewFragment wvf = new WebViewFragment();
 
        Intent i = this.getIntent();
        String link = i.getExtras().getString("link");
 
        Log.d("SwA", "URL ["+link+"]");
        wvf.init(link);
        getFragmentManager().beginTransaction().add(android.R.id.content, wvf).commit();
 
    }
 
}

Исходный код @ github .