我有一个应用程序,它的仪表板有 1 个或 2 个页面(根据用户配置文件)。
当应用程序第一次启动时,它只显示 3 个按钮,当信息到达时,它最多可以添加 3 个按钮(总共 6 个)。这些按钮有一个特定的顺序,应该保持:
- 按钮 1 是强制性的,应该是第一个;
- 按钮 2 是强制性的,应该是第二个;
- 按钮 6 是强制性的,应该是最后一个(当有 3、4、5 或 6 时)
- 按钮 3 到 5 不是强制性的,但应按其编号顺序显示。
为了产生这种效果,我有几个占位符来添加片段。每当一个新按钮到达(按钮 3 到 5)时,它就会进入 Button_6 的旧位置,该位置向前移动一个位置。
由于复杂的 UI 和行为,每个按钮都是一个片段。
该应用程序可以是纵向和横向的,纵向有 4 个按钮,横向有 3 个。每当应用程序旋转时,活动都会调用 refreshDashboard 方法来重绘 UI。
代码:
public class DashboardBuilder
{
private final static String TAG_BUTTON_1 = "BUTTON_1";
private final static String TAG_BUTTON_2 = "BUTTON_2";
private final static String TAG_BUTTON_3 = "BUTTON_3";
private final static String TAG_BUTTON_4 = "BUTTON_4";
private final static String TAG_BUTTON_5 = "BUTTON_5";
private final static String TAG_BUTTON_6 = "BUTTON_6";
private int width = 0;
private int height = 0;
private int pages = 0;
private int buttonsPerPage = 4;
private int orientation = Configuration.ORIENTATION_PORTRAIT;
public final String TAG = getClass().getSimpleName();
public DashboardBuilder(DashboardPaginationActivity dashboard)
{
FragmentManager.enableDebugLogging(false);
}
//This is called each time there is a rotation.
public void rebuildDashboard(DashboardPaginationActivity dashboard, int orientation)
{
switch (orientation)
{
case Configuration.ORIENTATION_LANDSCAPE:
case Configuration.ORIENTATION_SQUARE:
buttonsPerPage = 3;
break;
default:
buttonsPerPage = 4;
break;
}
this.width = dashboard.getWidth();
this.height = dashboard.getHeight();
// Orientation changed
if (this.orientation != orientation)
{
this.orientation = orientation;
pages = 0;
}
rebuildDashboard(dashboard);
}
private synchronized void rebuildDashboard(DashboardPaginationActivity dashboard)
{
final Application application = (Application) dashboard.getApplication();
final ArrayList<FragmentReference> fragmentsReferences = application.getFragmentsReferences();
if (fragmentsReferences.size() == 0)
{
try
{
final String[] tags = new String[] { TAG_BUTTON_1, TAG_BUTTON_2, TAG_BUTTON_3, TAG_BUTTON_4, TAG_BUTTON_5, TAG_BUTTON_6 };
for (String tag : tags)
{
Fragment fragment = dashboard.getSupportFragmentManager().findFragmentByTag(tag);
if (fragment != null)
{
LOG.debug(LOG.TAG_ADAPTER, this, "rebuildDashboard : delete tag -> " + tag, null);
fragmentsReferences.add(new FragmentReference(tag, fragment.getId()));
}
}
final ViewGroup pages = (ViewGroup) dashboard.findViewById(R.id.dashboard_pages);
pages.removeAllViews();
final ViewGroup pagination = (ViewGroup) dashboard.findViewById(R.id.pagination);
pagination.removeAllViews();
}
catch (Exception e)
{
}
// Definitely a first run
if (fragmentsReferences.size() == 0)
{
// add mandatory buttons + refresh layout
fragmentsReferences.add(new FragmentReference(TAG_BUTTON_1, -1));
fragmentsReferences.add(new FragmentReference(TAG_BUTTON_2, -1));
fragmentsReferences.add(new FragmentReference(TAG_BUTTON_6, -1));
}
refreshDashboard(dashboard, false);
}
else
{
// refresh layout
refreshDashboard(dashboard, true);
}
}
public void refreshDashboard(DashboardPaginationActivity dashboard, boolean exitOnNewButton)
{
final Application application = (Application) dashboard.getApplication();
final ArrayList<FragmentReference> fragmentsReferences = application.getFragmentsReferences();
int buttonsCounter = fragmentsReferences.size();
int pagesCounter = (buttonsCounter / (buttonsPerPage + 1)) + 1;
for (int i = pages; i < pagesCounter; i++)
{
final View pageInflator = createPage(dashboard, i);
if (pageInflator != null && i > 0)
{
swapIDs(i, pageInflator);
}
}
final FragmentManager manager = dashboard.getSupportFragmentManager();
final FragmentTransaction transaction = manager.beginTransaction();
for (int i = fragmentsReferences.size() - 1; i >= 0; i--)
{
Fragment fragment = null;
if (TAG_BUTTON_1.equalsIgnoreCase(fragmentsReferences.get(i).getTag()))
{
fragment = manager.findFragmentByTag(TAG_BUTTON_1);
if (fragment != null)
{
transaction.remove(fragment);
}
ButtonOneFragment button = new ButtonOneFragment();
button.setRetainInstance(false);
transaction.replace(getPlaceHolder(i), button, TAG_BUTTON_1);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentsReferences.get(i).setPlaceHolder(getPlaceHolder(i));
}
else if (TAG_BUTTON_2.equalsIgnoreCase(fragmentsReferences.get(i).getTag()))
{
fragment = manager.findFragmentByTag(TAG_BUTTON_2);
if (fragment != null)
{
transaction.remove(fragment);
}
ButtonTwoFragment button = new ButtonTwoFragment();
button.setRetainInstance(false);
transaction.replace(getPlaceHolder(i), button, TAG_BUTTON_2);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentsReferences.get(i).setPlaceHolder(getPlaceHolder(i));
}
else if (TAG_BUTTON_3.equalsIgnoreCase(fragmentsReferences.get(i).getTag()))
{
fragment = manager.findFragmentByTag(TAG_BUTTON_3);
if (fragment != null)
{
transaction.remove(fragment);
}
ButtonThreeFragment button = new ButtonThreeFragment();
button.setRetainInstance(false);
transaction.replace(getPlaceHolder(i), button, TAG_BUTTON_3);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentsReferences.get(i).setPlaceHolder(getPlaceHolder(i));
}
else if (TAG_BUTTON_4.equalsIgnoreCase(fragmentsReferences.get(i).getTag()))
{
fragment = manager.findFragmentByTag(TAG_BUTTON_4);
if (fragment != null)
{
transaction.remove(fragment);
}
ButtonFourFragment button = new ButtonFourFragment();
button.setRetainInstance(false);
transaction.replace(getPlaceHolder(i), button, TAG_BUTTON_4);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentsReferences.get(i).setPlaceHolder(getPlaceHolder(i));
}
else if (TAG_BUTTON_5.equalsIgnoreCase(fragmentsReferences.get(i).getTag()))
{
fragment = manager.findFragmentByTag(TAG_BUTTON_5);
if (fragment != null)
{
transaction.remove(fragment);
}
ButtonFiveFragment button = new ButtonFiveFragment();
button.setRetainInstance(false);
transaction.replace(getPlaceHolder(i), button, TAG_BUTTON_5);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentsReferences.get(i).setPlaceHolder(getPlaceHolder(i));
}
else if (TAG_BUTTON_6.equalsIgnoreCase(fragmentsReferences.get(i).getTag()))
{
fragment = manager.findFragmentByTag(TAG_BUTTON_6);
if (fragment != null)
{
transaction.remove(fragment);
}
ButtonSixFragment button = new ButtonSixFragment();
button.setRetainInstance(false);
transaction.replace(getPlaceHolder(i), button, TAG_BUTTON_6);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentsReferences.get(i).setPlaceHolder(getPlaceHolder(i));
}
if (exitOnNewButton)
{
// on first new button found
if (fragmentsReferences.get(i).getPlaceHolder() < 0) break;
}
}
buttonsCounter = fragmentsReferences.size();
pagesCounter = (buttonsCounter / (buttonsPerPage + 1)) + 1;
for (int i = pages; i > pagesCounter; i--)
{
// scroll to first if removing
dashboard.changePage(0);
removePage(dashboard, i - 1);
}
pages = pagesCounter;
dashboard.setTotalScreens(pages);
try
{
// we suspect that this is only required when a remove happens
// keep it here in all cases to be on the safe site...
transaction.commit();
manager.executePendingTransactions();
}
catch (Exception e)
{
// ignore exception
LOG.error(LOG.TAG_FRAGMENT, this, "refreshDashboard", e.getMessage());
// e.printStackTrace();
}
}
private View createPage(DashboardPaginationActivity dashboard, int index)
{
final View pageHolder = dashboard.getLayoutInflater().inflate(R.layout.dashboard_page, null);
pageHolder.setLayoutParams(new LinearLayout.LayoutParams(getHorizontalScrollWidth(dashboard), getHorizontalScrollHeight(dashboard)));
View existingView = null;
final View page = pageHolder.findViewById(R.id.page);
switch (index)
{
case 0:
if (dashboard.findViewById(R.id.page1) == null)
{
page.setId(R.id.page1);
}
existingView = dashboard.findViewById(R.id.page1);
break;
case 1:
if (dashboard.findViewById(R.id.page2) == null)
{
page.setId(R.id.page2);
}
existingView = dashboard.findViewById(R.id.page2);
break;
default:
break;
}
if (existingView != null)
{
return null;
}
// add page holder to dashboard
ViewGroup pages = ((ViewGroup) dashboard.findViewById(R.id.dashboard_pages));
pages.addView(pageHolder);
return pageHolder;
}
private void removePage(DashboardPaginationActivity dashboard, int index)
{
if (((ViewGroup) dashboard.findViewById(R.id.dashboard_pages)).getChildCount() > index)
{
((ViewGroup) dashboard.findViewById(R.id.dashboard_pages)).removeViewAt(index);
}
}
private void swapIDs(int index, View pageInflator)
{
if (orientation == Configuration.ORIENTATION_LANDSCAPE || orientation == Configuration.ORIENTATION_SQUARE)
{
swapLandscapeIDs(index, pageInflator);
}
else
{
swapPortraitIDs(index, pageInflator);
}
}
private Integer getFragmentPlaceHolder(DashboardPaginationActivity dashboard, String tag)
{
if (tag != null)
{
final Application application = (Application) dashboard.getApplication();
final ArrayList<FragmentReference> fragmentsReferences = application.getFragmentsReferences();
for (int i = 0; i < fragmentsReferences.size(); i++)
{
final String fragmentTag = fragmentsReferences.get(i).getTag();
if (tag.equalsIgnoreCase(fragmentTag))
{
return fragmentsReferences.get(i).getPlaceHolder();
}
}
}
return null;
}
private Integer addFragmentReference(DashboardPaginationActivity dashboard, String tag)
{
int index = 0;
if (tag != null)
{
final Application application = (Application) dashboard.getApplication();
final ArrayList<FragmentReference> fragmentsReferences = application.getFragmentsReferences();
if (tag.equalsIgnoreCase(TAG_BUTTON_1))
{
fragmentsReferences.add(0, new FragmentReference(TAG_BUTTON_1, -1));
index = 0;
}
if (tag.equalsIgnoreCase(TAG_BUTTON_2))
{
fragmentsReferences.add(1, new FragmentReference(TAG_BUTTON_2, -1));
index = 1;
}
if (tag.equalsIgnoreCase(TAG_BUTTON_3))
{
fragmentsReferences.add(2, new FragmentReference(TAG_BUTTON_3, -1));
index = 2;
}
else if (tag.equalsIgnoreCase(TAG_BUTTON_4))
{
if (TAG_BUTTON_3.equalsIgnoreCase(fragmentsReferences.get(2).getTag()))
{
fragmentsReferences.add(3, new FragmentReference(TAG_BUTTON_4, -1));
index = 3;
}
else
{
fragmentsReferences.add(2, new FragmentReference(TAG_BUTTON_4, -1));
index = 2;
}
}
else if (tag.equalsIgnoreCase(TAG_BUTTON_5))
{
if (fragmentsReferences.size() == 5 && TAG_BUTTON_4.equalsIgnoreCase(fragmentsReferences.get(3).getTag()))
{
fragmentsReferences.add(4, new FragmentReference(TAG_BUTTON_5, -1));
index = 4;
}
else if (fragmentsReferences.size() == 4 && (TAG_BUTTON_3.equalsIgnoreCase(fragmentsReferences.get(2).getTag()) || TAG_BUTTON_4.equalsIgnoreCase(fragmentsReferences.get(2).getTag())))
{
fragmentsReferences.add(3, new FragmentReference(TAG_BUTTON_5, -1));
index = 3;
}
else
{
fragmentsReferences.add(2, new FragmentReference(TAG_BUTTON_5, -1));
index = 2;
}
}
}
return index;
}
private void removeFragmentReference(DashboardPaginationActivity dashboard, String tag)
{
if (tag != null)
{
final Application application = (Application) dashboard.getApplication();
final ArrayList<FragmentReference> fragmentsReferences = application.getFragmentsReferences();
// start at 2 means that button 1 and 2 will never be removed
for (int i = 0; i < fragmentsReferences.size(); i++)
{
final String fragmentTag = fragmentsReferences.get(i).getTag();
if (tag.equalsIgnoreCase(fragmentTag))
{
fragmentsReferences.remove(i);
break;
}
}
final FragmentManager manager = dashboard.getSupportFragmentManager();
final Fragment fragment = manager.findFragmentByTag(tag);
if (fragment != null)
{
final FragmentTransaction transaction = manager.beginTransaction();
// remove store references and other resources
// ((ButtonFragment) fragment).cleanup();
transaction.remove(fragment);
transaction.commit();
try
{
// we suspect that this is only required when a remove happens
// keep it here in all cases to be on the safe site...
manager.executePendingTransactions();
}
catch (Exception e)
{
// ignore exception
LOG.debug(LOG.TAG_FRAGMENT, this, "removeFragmentReference", null);
}
}
}
}
public boolean buttonExists(DashboardPaginationActivity dashboard, String tag)
{
if (tag == null) return false;
final Application application = (Application) dashboard.getApplication();
final ArrayList<FragmentReference> fragmentsReferences = application.getFragmentsReferences();
for (int i = 0; i < fragmentsReferences.size(); i++)
{
final String fragmentTag = fragmentsReferences.get(i).getTag();
if (tag.equalsIgnoreCase(fragmentTag))
{
return true;
}
}
return false;
}
private int getPlaceHolder(int index)
{
switch (index)
{
case 0:
return R.id.placeHolder01;
case 1:
return R.id.placeHolder02;
case 2:
return R.id.placeHolder03;
case 3:
return R.id.placeHolder04;
case 4:
return R.id.placeHolder05;
case 5:
return R.id.placeHolder06;
case 6:
return R.id.placeHolder07;
case 7:
return R.id.placeHolder08;
case 8:
return R.id.placeHolder09;
case 9:
return R.id.placeHolder10;
case 10:
return R.id.placeHolder11;
case 11:
return R.id.placeHolder12;
default:
return -1;
}
}
private void swapPortraitIDs(int index, View pageInflated)
{
switch (index)
{
case 1:
pageInflated.findViewById(R.id.placeHolder01).setId(R.id.placeHolder05);
pageInflated.findViewById(R.id.placeHolder02).setId(R.id.placeHolder06);
pageInflated.findViewById(R.id.placeHolder03).setId(R.id.placeHolder07);
pageInflated.findViewById(R.id.placeHolder04).setId(R.id.placeHolder08);
break;
case 2:
pageInflated.findViewById(R.id.placeHolder01).setId(R.id.placeHolder09);
pageInflated.findViewById(R.id.placeHolder02).setId(R.id.placeHolder10);
pageInflated.findViewById(R.id.placeHolder03).setId(R.id.placeHolder11);
pageInflated.findViewById(R.id.placeHolder04).setId(R.id.placeHolder12);
break;
default:
LOG.error(LOG.TAG_ACTIVITY, activity, "swapPortraitIDs", "Invalid page number: " + index);
break;
}
}
private void swapLandscapeIDs(int index, View pageInflated)
{
switch (index)
{
case 1:
pageInflated.findViewById(R.id.placeHolder01).setId(R.id.placeHolder04);
pageInflated.findViewById(R.id.placeHolder02).setId(R.id.placeHolder05);
pageInflated.findViewById(R.id.placeHolder03).setId(R.id.placeHolder06);
break;
case 2:
pageInflated.findViewById(R.id.placeHolder01).setId(R.id.placeHolder07);
pageInflated.findViewById(R.id.placeHolder02).setId(R.id.placeHolder08);
pageInflated.findViewById(R.id.placeHolder03).setId(R.id.placeHolder09);
break;
default:
LOG.error(LOG.TAG_ACTIVITY, activity, "swapLandscapeIDs", "Invalid page number: " + index);
break;
}
}
public int[] getPagesIds(AbstractActivity activity)
{
switch (pages)
{
case 1:
return new int[] { R.id.page1 };
case 2:
return new int[] { R.id.page1, R.id.page2 };
default:
LOG.error(LOG.TAG_ACTIVITY, activity, "getPagesIds", "Invalid page number: " + pages);
return new int[0];
}
}
}
我不得不接受 manager.executePendingTransactions(); 使用 try catch 因为它会“随机”抛出 java.lang.IllegalStateException: Recursive entry to executePendingTransactions 如果在短时间内发生大量旋转。
我认为这是由于 Fragments Manager 的异步特性造成的。
我已经“解决”了这些尝试捕获的问题..我仍然想知道是否有更正确的方法来解决它和/或建议。
在 Try 捕获之前的 StackTrace:
19:04:47.402 E/AndroidRuntime( 1352): FATAL EXCEPTION: main
19:04:47.402 E/AndroidRuntime( 1352): java.lang.IllegalStateException: Recursive entry to executePendingTransactions
19:04:47.402 E/AndroidRuntime( 1352): at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1388)
19:04:47.402 E/AndroidRuntime( 1352): at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:420)
19:04:47.402 E/AndroidRuntime( 1352): at android.os.Handler.handleCallback(Handler.java:587)
19:04:47.402 E/AndroidRuntime( 1352): at android.os.Handler.dispatchMessage(Handler.java:92)
19:04:47.402 E/AndroidRuntime( 1352): at android.os.Looper.loop(Looper.java:123)
19:04:47.402 E/AndroidRuntime( 1352): at android.app.ActivityThread.main(ActivityThread.java:3683)
19:04:47.402 E/AndroidRuntime( 1352): at java.lang.reflect.Method.invokeNative(Native Method)
19:04:47.402 E/AndroidRuntime( 1352): at java.lang.reflect.Method.invoke(Method.java:507)
19:04:47.402 E/AndroidRuntime( 1352): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
19:04:47.402 E/AndroidRuntime( 1352): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
19:04:47.402 E/AndroidRuntime( 1352): at dalvik.system.NativeStart.main(Native Method)
19:04:47.406 W/ActivityManager( 107): Force finishing activity com.dashboard.tests/.activity.MainActivity
19:04:47.402 E/AndroidRuntime( 1352): FATAL EXCEPTION: main
19:04:47.402 E/AndroidRuntime( 1352): java.lang.IllegalStateException: Recursive entry to executePendingTransactions
19:04:47.402 E/AndroidRuntime( 1352): at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1388)
19:04:47.402 E/AndroidRuntime( 1352): at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:420)
根据 Android 开发人员办公时间 (http://www.google.com/moderator/#15/e=1ac28e&t=1ac28e.51) 的要求,这里是 Android 团队可以查看的代码。
谢谢
Ps:对不起,代码片段很长。