지원 라이브러리를 사용하지 않고 Android 4.0, 4.1 (<4.2)의 중첩 조각에 대한 모범 사례


115

지원 라이브러리 (필요하지 않은 경우) 를 사용하지 않고 따라서 4.x API 만 사용하는 4.0 및 4.1 태블릿 용 앱을 작성 중 입니다.

따라서 내 타겟 플랫폼은> = 4.0 및 <= 4.1로 매우 잘 정의됩니다.

앱에는 다중 창 레이아웃 (조각 2 개, 왼쪽에 작은 조각 1 개, 오른쪽에 콘텐츠 조각 1 개)과 탭이있는 작업 표시 줄이 있습니다.

이와 비슷합니다.

여기에 이미지 설명 입력

작업 표시 줄의 탭을 클릭하면 '외부'조각이 변경되고 내부 조각은 두 개의 중첩 조각 (1. 작은 왼쪽 목록 조각, 2. 넓은 콘텐츠 조각)이있는 조각입니다.

이제 조각, 특히 중첩 조각을 대체하는 가장 좋은 방법이 무엇인지 궁금합니다. ViewPager는 지원 라이브러리의 일부이며이 클래스에 대한 기본 4.x 대안은 없습니다. 내 의미에서 '사용되지 않는'것으로 보입니다. -http : //developer.android.com/reference/android/support/v4/view/ViewPager.html

그런 다음 Android 4.2에 대한 릴리스 노트를 읽었습니다. ChildFragmentManager, 이는 적합 할 것입니다.하지만 4.0과 4.1을 대상으로하고 있으므로 이것도 사용할 수 없습니다.

ChildFragmentManager 4.2에서만 사용할 수 있습니다.

안타깝게도 전체 Android 개발자 가이드에서도 지원 라이브러리없이 프래그먼트 사용에 대한 모범 사례를 보여주는 좋은 예는 거의 없습니다. 특히 중첩 된 조각과는 관련이 없습니다.

그래서 궁금합니다. 지원 라이브러리와 함께 제공되는 모든 것을 사용하지 않고 중첩 된 조각으로 4.1 앱을 작성할 수 없습니까? (Fragment 대신 FragmentActivity를 사용해야합니까?) 아니면 모범 사례는 무엇입니까?


현재 개발 중에있는 문제는 정확히 다음과 같습니다.

Android 지원 라이브러리는 이제 중첩 조각도 지원하므로 Android 1.6 이상에서 중첩 조각 디자인을 구현할 수 있습니다.

참고 : 레이아웃에 <fragment>. 중첩 된 조각은 조각에 동적으로 추가 될 때만 지원됩니다.

XML에 중첩 된 조각을 정의했기 때문에 분명히 다음과 같은 오류가 발생합니다.

Caused by: java.lang.IllegalArgumentException: Binary XML file line #15: Duplicate id 0x7f090009, tag frgCustomerList, or parent id 0x7f090008 with another fragment for de.xyz.is.android.fragment.CustomerListFragment_

지금은 스스로 결론을 내립니다. 4.1에서도 2.x 플랫폼을 대상으로하고 싶지 않을 때 스크린 샷에 표시된 중첩 조각은 지원 라이브러리 없이는 불가능합니다.

(이것은 실제로 질문 이라기보다 위키 항목에 가깝지만 다른 누군가가 전에 관리했을 수도 있습니다).

최신 정보:

유용한 답변 : Fragment Inside Fragment


22
세 가지 옵션이 있습니다. 1. 기본 중첩 조각으로 4.2 만 대상으로 지정합니다. 2. 지원 라이브러리의 중첩 조각이있는 4.x를 대상으로합니다. 3. 다른 플랫폼 대상 시나리오에는 중첩 조각을 사용하지 마십시오. 이것은 귀하의 질문에 대한 답이 될 것입니다. 또한 xml 레이아웃에 포함 된 중첩 조각을 사용할 수 없으며 모두 코드에 추가해야합니다. 지원 라이브러리없이 프래그먼트 사용에 대한 모범 사례를 보여주는 좋은 예는 거의 없습니다 . 지원 프래그먼트 프레임 워크는 기본 프레임 워크를 복제하므로 모든 예가 어느 쪽이든 작동해야합니다.
Luksprog 2013 년

@Luksprog 귀하의 의견에 감사드립니다. 나는 당신의 솔루션 2를 선호하고 프레임은 지원 라이브러리에서 잘 작동하지만 ActionBar의 탭은 그렇지 않습니다. t 4.x에 필요). 그리고 ActionBar.TabListener는 지원 라이브러리가 아닌 android.app.Fragment의 단편 만 지원합니다.
Mathias Conradt 2013 년

2
나는 Galaxy 탭의 연락처 앱에 익숙하지 않지만 항상 ActionBar(삼성에서 내장) 의 사용자 정의 구현에 직면 할 수 있음을 명심하십시오 . ActionBarSherlock을 자세히 살펴보면 공간이 있으면 ActionBar에 탭이 있습니다.
Luksprog 2013 년

4
@Luksprog 나는 당신이 이미 줄 유일한 대답을 제공했다고 믿습니다. 정답으로 그것을 넣으면 너무 친절합니까?
Warpzit 2013 년

1
@Pork 질문에 대한 주된 이유는 지원 라이브러리를 사용하지 않고도 중첩 조각에 대한 해결 방법이 있으며 다른 모든 뷰 요소입니다. 즉, 지원 라이브러리로 전환하면 Fragment 대신 FragmentActivity를 사용하게됩니다. 하지만 Fragment를 사용하고 싶습니다. 원하는 것은 Nested Fragments를 대체하는 것이지만 모든 v4 구성 요소는 아닙니다. 즉, 다른 오픈 소스 라이브러리 등을 통해. 예를 들어 위의 스크린 샷은 4.0에서 실행되며 ABS, SupportLib 또는 다른 것을 사용하고 있는지 궁금합니다.
Mathias Conradt 2013-06-13

답변:


60

한계

따라서 FragmentManager사용 하는 버전에 관계없이 xml에서는 다른 조각 내부에 조각을 중첩 할 수 없습니다.

따라서 코드를 통해 조각을 추가해야합니다. 이것은 문제처럼 보일 수 있지만 장기적으로는 레이아웃을 매우 유연하게 만듭니다.

그래서 사용하지 않고 중첩 getChildFragmentManger? 이면의 본질 childFragmentManager은 이전 조각 트랜잭션이 완료 될 때까지로드를 연기한다는 것입니다. 물론 4.2 또는 지원 라이브러리에서만 자연스럽게 지원되었습니다.

ChildManager없이 중첩-솔루션

물론입니다! 나는 ( ViewPager공표 이후) 오랫동안 이것을 해왔다 .

아래를 참조하십시오. 이것은 Fragment로딩을 지연 시키는 것이므로 Fragments를 내부에로드 할 수 있습니다.

Handler클래스는 매우 간단합니다. 정말 편리한 클래스입니다. 효과적으로 핸들러는 현재 프래그먼트 트랜잭션이 커밋을 마친 후 메인 스레드에서 공간이 실행될 때까지 기다립니다 (프래그먼트가 메인 스레드에서 실행되는 UI를 방해하기 때문입니다).

// Remember this is an example, you will need to modify to work with your code
private final Handler handler = new Handler();
private Runnable runPager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    return inflater.inflate(R.layout.frag_layout, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    super.onActivityCreated(savedInstanceState);
    runPager = new Runnable() {

        @Override
        public void run()
        {
          getFragmentManager().beginTransaction().addFragment(R.id.frag_container, MyFragment.newInstance()).commit();
        }
    };
    handler.post(runPager);
}

/**
 * @see android.support.v4.app.Fragment#onPause()
 */
@Override
public void onPause()
{
    super.onPause();
    handler.removeCallbacks(runPager);
}

나는 그것을 '모범 사례'라고 생각하지는 않지만이 해킹을 사용하는 라이브 앱을 가지고 있으며 아직 문제가 없습니다.

나는 또한이 방법을 사용하여 뷰 페이저를 포함합니다-https: //gist.github.com/chrisjenx/3405429


레이아웃 중첩 조각을 어떻게 처리합니까?
pablisco

이 작업을 볼 수있는 유일한 방법은 CustomLayoutInflater를 사용하는 것입니다. fragment요소를 발견하면 수퍼 구현을 재정의하고 직접 구문 분석 / 팽창을 시도합니다. 그러나 그것은 많은 노력이 될 것입니다. StackOverflow 질문의 범위를 벗어납니다.
Chris.Jenkins

안녕하세요, 누구든지이 문제에서 나를 도울 수 있습니까 ?? 나는 .. 정말 붙어 stackoverflow.com/questions/32240138/...
닉스

2

API 17 이전에서이를 수행하는 가장 좋은 방법은 아예 수행하지 않는 것입니다. 이 동작을 구현하려고하면 문제가 발생합니다. 그러나 현재 API 14를 사용하여 설득력있게 위조 될 수 없다는 것은 아닙니다. 제가 한 일은 다음과 같습니다.

1-프래그먼트 간의 통신을 살펴보십시오. http://developer.android.com/training/basics/fragments/communicating.html

2-레이아웃 xml FrameLayout을 기존 Fragment에서 Activity 레이아웃으로 이동하고 높이를 0으로 지정하여 숨 깁니다.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent">
<FrameLayout android:id="@+id/content"
          android:layout_width="300dp"
          android:layout_height="match_parent" />


<FrameLayout android:id="@+id/lstResults"
             android:layout_width="300dp"
             android:layout_height="0dp"
             android:layout_below="@+id/content"
             tools:layout="@layout/treeview_list_content"/>


<FrameLayout android:id="@+id/anomalies_fragment"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
        android:layout_toRightOf="@+id/content" />

3-상위 프래그먼트에서 인터페이스 구현

    OnListener mCallback;

// Container Activity must implement this interface
public interface OnListener 
{
    public void onDoSomethingToInitChildFrame(/*parameters*/);
    public void showResults();
    public void hideResults();
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);

    // This makes sure that the container activity has implemented
    // the callback interface. If not, it throws an exception
    try {
        mCallback = (OnFilterAppliedListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement OnListener");
    }
}

@Override
public void onActivityCreated(Bundle savedInstanceState) 
{
    super.onActivityCreated(savedInstanceState);

    mCallback.showResults();
}

@Override
public void onPause()
{
    super.onPause();

    mCallback.hideResults();
}

public void onClickButton(View view)
{
    // do click action here

    mCallback.onDoSomethingToInitChildFrame(/*parameters*/);
}

4-상위 활동에서 인터페이스 구현

공용 클래스 YourActivity extends Activity implements yourParentFragment.OnListener {

public void onDoSomethingToInitChildFrame(/*parameters*/)
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment == null)
    {
        childFragment = new yourChildFragment(/*parameters*/);
        ft.add(R.id.lstResults, childFragment, "Results");
    }
    else
    {
        ft.detach(childFragment);

        ((yourChildFragment)childFragment).ResetContent(/*parameters*/);

        ft.attach(childFragment);
    }
    ft.commit();

    showResultsPane();
}

public void showResults()
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.attach(childFragment);
    ft.commit();

    showResultsPane();
}

public void showResultsPane()
{
    //resize the elements to show the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
    findViewById(R.id.lstResults).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}

public void hideResults()
{
    //resize the elements to hide the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
    findViewById(R.id.lstResults).getLayoutParams().height = 0;

    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.detach(childFragment);
    ft.commit();
}

}

5-이 메서드를 사용하면 API 17 이전 환경에서 getChildFragmentManager () 함수와 동일한 유동적 인 기능을 얻을 수 있습니다. 아시다시피 자식 조각은 더 이상 부모 조각의 자식이 아니지만 이제는 활동의 자식이므로 피할 수 없습니다.


1

TabHost로 인해 지원 라이브러리 사용에 문제가있는 NavigationDrawer, TabHost 및 ViewPager의 조합으로 인해이 정확한 문제를 처리해야했습니다. 그런 다음 JellyBean 4.1의 최소 API도 지원해야했기 때문에 getChildFragmentManager와 함께 중첩 된 조각을 사용하는 것은 옵션이 아닙니다.

그래서 내 문제는 ...

TabHost (최상위 수준 용)
+ ViewPager (최상위 탭 조각 중 하나만 해당)
= Nested Fragment 필요 (JellyBean 4.1은 지원하지 않음)

내 해결책은 실제로 조각을 중첩하지 않고 중첩 조각의 환상을 만드는 것이 었습니다. 주요 활동이 TabHost 및 ViewPager를 사용하여 layout_weight를 0과 1 사이로 전환하여 가시성이 관리되는 두 형제 뷰를 관리하도록함으로써이 작업을 수행했습니다.

//Hide the fragment used by TabHost by setting height and weight to 0
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 0);
mTabHostedView.setLayoutParams(lp);
//Show the fragment used by ViewPager by setting height to 0 but weight to 1
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
mPagedView.setLayoutParams(lp);

이를 통해 관련 레이아웃 가중치를 수동으로 관리하는 한 가짜 "중첩 조각"이 독립적 인보기로 작동 할 수있었습니다.

내 activity_main.xml은 다음과 같습니다.

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.ringofblades.stackoverflow.app.MainActivity">

    <TabHost
        android:id="@android:id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <FrameLayout android:id="@android:id/tabcontent"
                android:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"/>
            <android.support.v4.view.ViewPager
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/pager"
                android:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"
                tools:context="com.ringofblades.stackoverflow.app.MainActivity">
                <FrameLayout
                    android:id="@+id/container"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
            </android.support.v4.view.ViewPager>
            <TabWidget android:id="@android:id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </TabHost>

    <fragment android:id="@+id/navigation_drawer"
        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:name="com.ringofblades.stackoverflow.app.NavigationDrawerFragment"
        tools:layout="@layout/fragment_navigation_drawer" />
</android.support.v4.widget.DrawerLayout>

"@ + id / pager"및 "@ + id / container"는 'android : layout_weight = "0.5"'및 'android : layout_height = "0dp"'가있는 형제입니다. 이것은 모든 화면 크기에 대해 미리보기에서 볼 수 있도록하기위한 것입니다. 어쨌든 그들의 가중치는 런타임 중에 코드에서 조작됩니다.


안녕하세요, 왜 탭과 함께 ActionBar 대신 TabHost를 사용하기로 선택했는지 궁금합니다. 나 자신이 TabHost에서 ActionBar로 전환했고, 내 코드가 더 깔끔하고 콤팩트
해졌습니다

내가 기억하는 한, ActionBar에서 탭을 사용하는 한 가지 단점은 자동으로 회 전자 드롭 다운 메뉴 (작은 화면의 경우)로 표시하기로 결정하고 나에게 좋지 않다는 것입니다. 하지만 100 % 확실하지 않습니다.
WindRider

@Igor, 내가 어딘가에 여기 읽을 SO것을 사용하여 ActionBarA의 탭 Navigation Drawer이 자동으로 서랍의 뷰 위에 탭을 배치하기 때문에 좋지 않다. 죄송합니다. 백업 할 링크가 없습니다.
Azurespot 2015

1
@NoniA. blog.xamarin.com/android-tips-hello-toolbar-goodbye-action-bar Android의 개발을 전혀 따르고 있습니까?
IgorGanapolsky

1
와, @Igor 링크 주셔서 감사합니다! 나는 이것을 확실히 확인할 것이다. 나는 아직 초보자이기 때문에 안드로이드로 배울 수있는 것이 백만 가지가 있지만 이것은 보석처럼 보인다! 다시 한 번 감사드립니다.
Azurespot

1

@ Chris.Jenkins 답변을 바탕으로 이것은 수명주기 이벤트 (IllegalStateExceptions를 던지는 경향이 있음) 중에 조각을 제거하기 위해 잘 작동하는 솔루션입니다. 이것은 Handler 접근 방식과 Activity.isFinishing () 검사의 조합을 사용합니다 (그렇지 않으면 "OnSaveInstanceState 후에이 작업을 수행 할 수 없음"에 대한 오류가 발생합니다).

import android.app.Activity;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

public abstract class BaseFragment extends Fragment {
    private final Handler handler = new Handler();

    /**
     * Removes the {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragment The {@link Fragment} to schedule for removal.
     */
    protected void removeFragment(@Nullable final Fragment fragment) {
        if (fragment == null) return;

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    getFragmentManager().beginTransaction()
                            .remove(fragment)
                            .commitAllowingStateLoss();
                }
            }
        });
    }

    /**
     * Removes each {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragments The {@link Fragment}s to schedule for removal.
     */
    protected void removeFragments(final Fragment... fragments) {
        final FragmentManager fragmentManager = getFragmentManager();
        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        for (Fragment fragment : fragments) {
            if (fragment != null) {
                fragmentTransaction.remove(fragment);
            }
        }

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    fragmentTransaction.commitAllowingStateLoss();
                }
            }
        });
    }
}

용법:

class MyFragment extends Fragment {
    @Override
    public void onDestroyView() {
        removeFragments(mFragment1, mFragment2, mFragment3);
        super.onDestroyView();
    }
}

1

OP가 지원 라이브러리를 사용하지 못하게하는 특수한 상황이있을 수 있지만 대부분의 사람들이 사용해야합니다. Android 문서에서는이를 권장하며 최대한 많은 사용자가 앱을 사용할 수 있도록합니다.

에서 여기 내 풀러 대답 나는 지원 라이브러리와 중첩 조각을 사용하는 방법을 보여주는 예를했다.

여기에 이미지 설명 입력


나는 OP였다. 지원 라이브러리를 사용하지 않은 이유는 사용 된 하드웨어가> = 4.0 및 <= 4.1로 명확하게 정의 된 회사 내부 앱이기 때문입니다. 광범위한 청중에게 다가 갈 필요가 없었고 내부 직원이었고 회사 외부에서 앱을 사용할 의도가 없었습니다. 지원 라이브러리의 유일한 이유는 이전 버전과 호환되는 것입니다.하지만 지원 라이브러리로 할 수있는 모든 작업을 지원 라이브러리없이 "기본적으로"달성 할 수 있어야합니다. "네이티브"상위 버전은 하위 호환만을 목적으로하는 지원 라이브러리보다 기능이 적은 이유는 무엇입니까?
Mathias Conradt

1
그럼에도 불구하고 물론 지원 라이브러리를 사용할 수 있으며 저도 사용할 수 있습니다. Google이 지원 라이브러리에서만 기능을 제공하고 외부는 제공하지 않는 이유를 이해하지 못했거나 어쨌든 모범 사례라면 지원 라이브러리라고 부르고 전체 표준으로 만들지 않은 이유를 이해하지 못했습니다. 다음은 지원 라이브러리에 대한 좋은 기사입니다. martiancraft.com/blog/2015/06/android-support-library
Mathias Conradt

@Mathias, 좋은 기사.
Suragch
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.