我正在使用来自 Android Developers 网站的以下示例:
运行应用程序时,我不断收到此错误:
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()
方法,或者有更好的方法来完成这个?
感谢您对此的任何建议。我想知道为什么这个例子不起作用。
附上我的代码:
主要活动
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);
}
}
}
活动库
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");
}
}