导致错误的Android SwipeRefreshLayout示例:“指定的子级已有父级。”

时间:2014-08-30 23:44:59

标签: android android-fragments fragment swiperefreshlayout

我正在使用Android开发者网站上的以下示例:

SwipeRefreshListFragment Official Example

运行应用程序时,我不断收到此错误:

java.lang.RuntimeException: Unable to start activity ComponentInfo{example.com.app/example.com.app.MainActivity}: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2195)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245)
        at android.app.ActivityThread.access$800(ActivityThread.java:135)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5017)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
        at dalvik.system.NativeStart.main(Native Method)
 Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
        at android.view.ViewGroup.addViewInner(ViewGroup.java:3562)
        at android.view.ViewGroup.addView(ViewGroup.java:3415)
        at android.view.ViewGroup.addView(ViewGroup.java:3360)
        at android.view.ViewGroup.addView(ViewGroup.java:3336)
        at android.support.v4.app.NoSaveStateFrameLayout.wrap(NoSaveStateFrameLayout.java:40)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:942)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1115)
        at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
        at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1478)
        at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:570)
        at example.com.taskdata.activities.ActivityBase.onStart(ActivityBase.java:23)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1171)
        at android.app.Activity.performStart(Activity.java:5241)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2168)

代码是正确的,它类似于页面上显示的示例。我所有的课程都有适当的进口。我没有在任何地方使用setContentView()两次。我需要在哪里添加removeView()方法,还是有更好的方法来完成此操作?

对此提出任何建议。我想知道为什么这个例子不起作用。

附上我的代码:

MainActivity

public class MainActivity extends ActivityBase {

public static final String TAG = "MainActivity";

// Whether log fragment is shown
private boolean mLogShown;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    FragmentTransaction fragmentTransaction = getSupportFragmentManager()
            .beginTransaction();
    TaskListFragment fragment = new TaskListFragment();
    fragmentTransaction.replace(R.id.container, fragment).commit();

    **MY GUESS IS THE PROBLEM IS HERE ^^^^**
}


@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
    MenuItem logToggle = menu.findItem(R.id.action_settings);
    logToggle.setVisible(findViewById(R.id.output) instanceof ViewAnimator);
    logToggle.setTitle(mLogShown ? R.string.hide_log : R.string.show_log);

    return super.onPrepareOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    switch (item.getItemId()) {
        case R.id.action_settings:
            mLogShown = !mLogShown;
            ViewAnimator output = (ViewAnimator) findViewById(R.id.output);
            if (mLogShown) {
                output.setDisplayedChild(1);
            } else {
                output.setDisplayedChild(0);
            }
            supportInvalidateOptionsMenu();
            return true;
    }
    return super.onOptionsItemSelected(item);
}

/*
Create a chain of targets that will receive log data
 */
@Override
public void initializeLogging() {
    // Wraps Android's native framework
    LogWrapper logWrapper = new LogWrapper();
    // Using Log, front-end to the logging chain, emulates android.util.Log method signatures
    Log.setLogNode(logWrapper);

    // Filter strips out everything except the message text
    MessageOnlyLogFilter filter = new MessageOnlyLogFilter();
    logWrapper.setNext(filter);

    // On screen logging via fragment with a textview
    LogFragment logFragment = (LogFragment) getSupportFragmentManager()
            .findFragmentById(R.id.logFragment);
    filter.setNext(logFragment.getLogView());

    Log.i(TAG, "Ready");
}
}

SwipeRefreshListFragment

public class SwipeRefreshListFragment extends ListFragment {

private static final String TAG = "SwipeRefreshFragment";

// ListFragment where user can swipe up to refresh list
private SwipeRefreshLayout refreshTaskList;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    // Create list fragment's content view by calling super method
    final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);

    // Now create a SwipeRefreshLayout to wrap the fragment's content view
    refreshTaskList = new ListFragmentSwipeRefreshLayout(container.getContext());

    // Add the list fragment's content view to the SwipeRefreshLayout, making sure that it fills
    // the SwipeRefreshLayout
    refreshTaskList.addView(listFragmentView, ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT);

    // Now make sure that the SwipeRefreshLayout fills the Fragment
    refreshTaskList.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT));

    return listFragmentView;
}

// Set onRefreshListener to listen for iniated refreshes by user
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener) {
    refreshTaskList.setOnRefreshListener(listener);
}

// Method returns whether or not the SwipeRefreshLayout is refreshing or not
public boolean isRefreshing() {
    return refreshTaskList.isRefreshing();
}

// Set whether the SwipeRefreshLayout should be displaying items it is refreshing
public void setRefreshing(boolean refreshing) {
    refreshTaskList.setRefreshing(refreshing);
}

// Set the color scheme of the Refresh on SwipeRefreshLayout (colors shown at top when refreshing)
public void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) {
    refreshTaskList.setColorScheme(colorRes1, colorRes2, colorRes3, colorRes4);
}

// Return the Fragment's Widget
public SwipeRefreshLayout getSwipeRefreshLayout() {
    return refreshTaskList;
}

/*
Subclass of SwipeRefreshLayout, for use in ListFragment. Needed because SwipeRefreshLayout only supports a single
child, which it expects to be the one which triggers refreshes. In this case the layout's child is content view
returned from ListFragment in onCreateView() which is a ViewGroup

To enable 'swipe-to-refresh' suppoer we need to override the default behavior and properly signal when a gesture is
possible. This is done by override canChildScrollUp()
 */
private class ListFragmentSwipeRefreshLayout extends SwipeRefreshLayout {

    public ListFragmentSwipeRefreshLayout(Context context) {
        super(context);
    }

    @Override
    public boolean canChildScrollUp() {
        final ListView listView = getListView();
        if (listView.getVisibility() == View.VISIBLE) {
            return canListViewScrollUp(listView);
        } else {
            return false;
        }
    }

}

/*
Utility method to check whether a ListView can scroll up from it's current position.
Handles perform version differences, providing backwards compatability where needed.
 */
private static boolean canListViewScrollUp(ListView listView) {
    if (Build.VERSION.SDK_INT >= 14) {
        // For IceCream Sandwich and above we can call canScrollVertically() to determine this
        return ViewCompat.canScrollVertically(listView, -1); // Scroll in the -1 direction
    } else {
        // Pre-ICS we need to manually check the first visible item and the child's view top value
        return listView.getChildCount() > 0 && (listView.getFirstVisiblePosition() > 0
                || listView.getChildAt(0).getTop() < listView.getPaddingTop());
    }
}

}

SwipeRefreshListFragmentFragment

public class SwipeRefreshListFragmentFragment extends SwipeRefreshListFragment {

private final static String TAG = "TaskListFragment";

private static final int LIST_ITEM_COUNT = 20;

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

    // Notify System to allow options menu for this Fragment
    setHasOptionsMenu(true);

}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    /*
    Create ArrayAdapter to contain the data for the Listview. Each item in the Listview
    uses the System defined list_item.xml
     */
    ListAdapter adapter = new ArrayAdapter<String>(
            getActivity(),
            android.R.layout.simple_list_item_1,
            Tasks.randomList(LIST_ITEM_COUNT)
    );

    // Set the adapter between ListView and the backing data
    setListAdapter(adapter);

    /*
    Implement SwipeRefreshLayout.OnRefreshListener when users do the 'swipe-to-refresh'
    gesture, SwipeRefreshLayout invokes onRefresh(). In onRefresh(), call a method that
    refreshes the content. Call the same method in response to the Refresh action from the
    action bar.
     */
    setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            Log.i(TAG, "onRefresh called from SwipeRefreshLayout");
            initiateRefresh();
        }
    });
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.main_menu, menu);
}

/*
Responds to user's selection of its refresh action item, Start the SwipeRefreshLayout
progress bar, then initiate the background task that refreshes the content.

A color scheme menu item used for demonstrating the use of SwipeRefreshLayout's color scheme
Color scheme should match branding
 */
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_refresh:
            Log.i(TAG, "Refresh menu item selected");

            // We make sure that the SwipeRefreshLayout is displaying its refreshing indicator
            if (!isRefreshing()) {
                setRefreshing(true);
            }

            // Start our refresh background task
            initiateRefresh();
            return true;

        case R.id.menu_color_scheme_1:
            Log.i(TAG, "setColorScheme #1");
            item.setChecked(true);

            // Change the colors displayed by the SwipeRefreshLayout by providing it with 4
            // color resource Ids
            setColorScheme(R.color.color_scheme_1_1, R.color.color_scheme_1_2,
                    R.color.color_scheme_1_3, R.color.color_scheme_1_4);
            return true;

        case R.id.menu_color_scheme_2:
            Log.i(TAG, "setColorScheme #2");
            item.setChecked(true);

            // Change the colors displayed by the SwipeRefreshLayout by providing it with 4
            // color resource ids
            setColorScheme(R.color.color_scheme_2_1, R.color.color_scheme_2_2,
                    R.color.color_scheme_2_3, R.color.color_scheme_2_4);
            return true;

        case R.id.menu_color_scheme_3:
            Log.i(TAG, "setColorScheme #3");
            item.setChecked(true);

            // Change the colors displayed by the SwipeRefreshLayout by providing it with 4
            // color resource ids
            setColorScheme(R.color.color_scheme_3_1, R.color.color_scheme_3_2,
                    R.color.color_scheme_3_3, R.color.color_scheme_3_4);
            return true;
    }

    return super.onOptionsItemSelected(item);

}

/*
By abstracting the refresh process to a single method, the app allows both the SwipeGestureLayout onRefresh()
method and the Refresh action item to refresh the content
 */
private void initiateRefresh() {
    Log.i(TAG, "initiateRefresh");

    /*
    Execute the background task, which uses AsyncTask to load data
     */
    new BackgroundTask().execute();
}

/*
When the asynctask finishes, it finishes onRefreshComplete() which updates the data in the ListAdapter
and turns off the progress bar
 */
private void onRefreshComplete(List<String> result) {
    Log.i(TAG, "onRefreshComplete");

    // Remove all items from the ListAdapter, and then replace them with new items
    ArrayAdapter<String> adapter = (ArrayAdapter<String>) getListAdapter();
    adapter.clear();
    for (String task : result) {
        adapter.add(task);
    }

    // Stop refreshing the indicator
    setRefreshing(false);
}

/*
AsyncTask which simulates a long running task to fetch new construction tasks
 */
private class BackgroundTask extends AsyncTask<Void, Void, List<String>> {

    static final int TASK_DURATION = 3 * 1000; // 3 seconds

    @Override
    protected List<String> doInBackground(Void... params) {
        // Sleep for a small amount of time to simulate a background task
        try {
            Thread.sleep(TASK_DURATION);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Return a new random list of tasks
        return Tasks.randomList(LIST_ITEM_COUNT);
    }

    @Override
    protected void onPostExecute(List<String> result) {
        super.onPostExecute(result);

        // Tell the fragment that the refresh has completed
        onRefreshComplete(result);
    }

}

}

ACTIVITYBASE

public class ActivityBase extends FragmentActivity {

public static final String TAG = "ActivityBase";

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

@Override
protected void onStart() {
    super.onStart();
    initializeLogging();
}

// Set targets to receive data
public void initializeLogging() {
    // Using Log, front-end to the logging chain, emulates android.util.log method signatures
    // Wraps Android's native log framework
    LogWrapper logWrapper = new LogWrapper();
    Log.setLogNode(logWrapper);

    Log.i(TAG, "Ready");
}

}

1 个答案:

答案 0 :(得分:0)

SwipeRefreshLayout只能有一个child.if你想添加多个孩子(这不是一个好习惯,应该避免)尝试做一个linearlayout并添加子项。